test
This commit is contained in:
@@ -0,0 +1,480 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
////TODO: array support
|
||||
////TODO: delimiter support
|
||||
////TODO: designator support
|
||||
|
||||
#pragma warning disable CS0649
|
||||
namespace UnityEngine.InputSystem.HID
|
||||
{
|
||||
/// <summary>
|
||||
/// Turns binary HID descriptors into <see cref="HID.HIDDeviceDescriptor"/> instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For information about the format, see the <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf">
|
||||
/// Device Class Definition for Human Interface Devices</a> section 6.2.2.
|
||||
/// </remarks>
|
||||
internal static class HIDParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse a HID report descriptor as defined by section 6.2.2 of the
|
||||
/// <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf">HID
|
||||
/// specification</a> and add the elements and collections from the
|
||||
/// descriptor to the given <paramref name="deviceDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer containing raw HID report descriptor.</param>
|
||||
/// <param name="deviceDescriptor">HID device descriptor to complete with the information
|
||||
/// from the report descriptor. Elements and collections will get added to this descriptor.</param>
|
||||
/// <returns>True if the report descriptor was successfully parsed.</returns>
|
||||
/// <remarks>
|
||||
/// Will also set <see cref="HID.HIDDeviceDescriptor.inputReportSize"/>,
|
||||
/// <see cref="HID.HIDDeviceDescriptor.outputReportSize"/>, and
|
||||
/// <see cref="HID.HIDDeviceDescriptor.featureReportSize"/>.
|
||||
/// </remarks>
|
||||
public static unsafe bool ParseReportDescriptor(byte[] buffer, ref HID.HIDDeviceDescriptor deviceDescriptor)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
fixed(byte* bufferPtr = buffer)
|
||||
{
|
||||
return ParseReportDescriptor(bufferPtr, buffer.Length, ref deviceDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe static bool ParseReportDescriptor(byte* bufferPtr, int bufferLength, ref HID.HIDDeviceDescriptor deviceDescriptor)
|
||||
{
|
||||
// Item state.
|
||||
var localItemState = new HIDItemStateLocal();
|
||||
var globalItemState = new HIDItemStateGlobal();
|
||||
|
||||
// Lists where we accumulate the data from the HID items.
|
||||
var reports = new List<HIDReportData>();
|
||||
var elements = new List<HID.HIDElementDescriptor>();
|
||||
var collections = new List<HID.HIDCollectionDescriptor>();
|
||||
var currentCollection = -1;
|
||||
|
||||
// Parse the linear list of items.
|
||||
var endPtr = bufferPtr + bufferLength;
|
||||
var currentPtr = bufferPtr;
|
||||
while (currentPtr < endPtr)
|
||||
{
|
||||
var firstByte = *currentPtr;
|
||||
|
||||
////TODO
|
||||
if (firstByte == 0xFE)
|
||||
throw new NotImplementedException("long item support");
|
||||
|
||||
// Read item header.
|
||||
var itemSize = (byte)(firstByte & 0x3);
|
||||
var itemTypeAndTag = (byte)(firstByte & 0xFC);
|
||||
++currentPtr;
|
||||
|
||||
// Process item.
|
||||
switch (itemTypeAndTag)
|
||||
{
|
||||
// ------------ Global Items --------------
|
||||
// These set item state permanently until it is reset.
|
||||
|
||||
// Usage Page
|
||||
case (int)HIDItemTypeAndTag.UsagePage:
|
||||
globalItemState.usagePage = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Report Count
|
||||
case (int)HIDItemTypeAndTag.ReportCount:
|
||||
globalItemState.reportCount = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Report Size
|
||||
case (int)HIDItemTypeAndTag.ReportSize:
|
||||
globalItemState.reportSize = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Report ID
|
||||
case (int)HIDItemTypeAndTag.ReportID:
|
||||
globalItemState.reportId = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Logical Minimum
|
||||
case (int)HIDItemTypeAndTag.LogicalMinimum:
|
||||
globalItemState.logicalMinimum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Logical Maximum
|
||||
case (int)HIDItemTypeAndTag.LogicalMaximum:
|
||||
globalItemState.logicalMaximum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Physical Minimum
|
||||
case (int)HIDItemTypeAndTag.PhysicalMinimum:
|
||||
globalItemState.physicalMinimum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Physical Maximum
|
||||
case (int)HIDItemTypeAndTag.PhysicalMaximum:
|
||||
globalItemState.physicalMaximum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Unit Exponent
|
||||
case (int)HIDItemTypeAndTag.UnitExponent:
|
||||
globalItemState.unitExponent = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Unit
|
||||
case (int)HIDItemTypeAndTag.Unit:
|
||||
globalItemState.unit = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// ------------ Local Items --------------
|
||||
// These set the state for the very next elements to be generated.
|
||||
|
||||
// Usage
|
||||
case (int)HIDItemTypeAndTag.Usage:
|
||||
localItemState.SetUsage(ReadData(itemSize, currentPtr, endPtr));
|
||||
break;
|
||||
|
||||
// Usage Minimum
|
||||
case (int)HIDItemTypeAndTag.UsageMinimum:
|
||||
localItemState.usageMinimum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// Usage Maximum
|
||||
case (int)HIDItemTypeAndTag.UsageMaximum:
|
||||
localItemState.usageMaximum = ReadData(itemSize, currentPtr, endPtr);
|
||||
break;
|
||||
|
||||
// ------------ Main Items --------------
|
||||
// These emit things into the descriptor based on the local and global item state.
|
||||
|
||||
// Collection
|
||||
case (int)HIDItemTypeAndTag.Collection:
|
||||
|
||||
// Start new collection.
|
||||
var parentCollection = currentCollection;
|
||||
currentCollection = collections.Count;
|
||||
collections.Add(new HID.HIDCollectionDescriptor
|
||||
{
|
||||
type = (HID.HIDCollectionType)ReadData(itemSize, currentPtr, endPtr),
|
||||
parent = parentCollection,
|
||||
usagePage = globalItemState.GetUsagePage(0, ref localItemState),
|
||||
usage = localItemState.GetUsage(0),
|
||||
firstChild = elements.Count
|
||||
});
|
||||
|
||||
HIDItemStateLocal.Reset(ref localItemState);
|
||||
break;
|
||||
|
||||
// EndCollection
|
||||
case (int)HIDItemTypeAndTag.EndCollection:
|
||||
if (currentCollection == -1)
|
||||
return false;
|
||||
|
||||
// Close collection.
|
||||
var collection = collections[currentCollection];
|
||||
collection.childCount = elements.Count - collection.firstChild;
|
||||
collections[currentCollection] = collection;
|
||||
|
||||
// Switch back to parent collection (if any).
|
||||
currentCollection = collection.parent;
|
||||
|
||||
HIDItemStateLocal.Reset(ref localItemState);
|
||||
break;
|
||||
|
||||
// Input/Output/Feature
|
||||
case (int)HIDItemTypeAndTag.Input:
|
||||
case (int)HIDItemTypeAndTag.Output:
|
||||
case (int)HIDItemTypeAndTag.Feature:
|
||||
|
||||
// Determine report type.
|
||||
var reportType = itemTypeAndTag == (int)HIDItemTypeAndTag.Input
|
||||
? HID.HIDReportType.Input
|
||||
: itemTypeAndTag == (int)HIDItemTypeAndTag.Output
|
||||
? HID.HIDReportType.Output
|
||||
: HID.HIDReportType.Feature;
|
||||
|
||||
// Find report.
|
||||
var reportIndex = HIDReportData.FindOrAddReport(globalItemState.reportId, reportType, reports);
|
||||
var report = reports[reportIndex];
|
||||
|
||||
// If we have a report ID, then reports start with an 8 byte report ID.
|
||||
// Shift our offsets accordingly.
|
||||
if (report.currentBitOffset == 0 && globalItemState.reportId.HasValue)
|
||||
report.currentBitOffset = 8;
|
||||
|
||||
// Add elements to report.
|
||||
var reportCount = globalItemState.reportCount.GetValueOrDefault(1);
|
||||
var flags = ReadData(itemSize, currentPtr, endPtr);
|
||||
for (var i = 0; i < reportCount; ++i)
|
||||
{
|
||||
var element = new HID.HIDElementDescriptor
|
||||
{
|
||||
usage = localItemState.GetUsage(i) & 0xFFFF, // Mask off usage page, if set.
|
||||
usagePage = globalItemState.GetUsagePage(i, ref localItemState),
|
||||
reportType = reportType,
|
||||
reportSizeInBits = globalItemState.reportSize.GetValueOrDefault(8),
|
||||
reportOffsetInBits = report.currentBitOffset,
|
||||
reportId = globalItemState.reportId.GetValueOrDefault(1),
|
||||
flags = (HID.HIDElementFlags)flags,
|
||||
logicalMin = globalItemState.logicalMinimum.GetValueOrDefault(0),
|
||||
logicalMax = globalItemState.logicalMaximum.GetValueOrDefault(0),
|
||||
physicalMin = globalItemState.GetPhysicalMin(),
|
||||
physicalMax = globalItemState.GetPhysicalMax(),
|
||||
unitExponent = globalItemState.unitExponent.GetValueOrDefault(0),
|
||||
unit = globalItemState.unit.GetValueOrDefault(0),
|
||||
};
|
||||
report.currentBitOffset += element.reportSizeInBits;
|
||||
elements.Add(element);
|
||||
}
|
||||
reports[reportIndex] = report;
|
||||
|
||||
HIDItemStateLocal.Reset(ref localItemState);
|
||||
break;
|
||||
}
|
||||
|
||||
if (itemSize == 3)
|
||||
currentPtr += 4;
|
||||
else
|
||||
currentPtr += itemSize;
|
||||
}
|
||||
|
||||
deviceDescriptor.elements = elements.ToArray();
|
||||
deviceDescriptor.collections = collections.ToArray();
|
||||
|
||||
// Set usage and usage page on device descriptor to what's
|
||||
// on the toplevel application collection.
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
if (collection.parent == -1 && collection.type == HID.HIDCollectionType.Application)
|
||||
{
|
||||
deviceDescriptor.usage = collection.usage;
|
||||
deviceDescriptor.usagePage = collection.usagePage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe static int ReadData(int itemSize, byte* currentPtr, byte* endPtr)
|
||||
{
|
||||
if (itemSize == 0)
|
||||
return 0;
|
||||
|
||||
// Read byte.
|
||||
if (itemSize == 1)
|
||||
{
|
||||
if (currentPtr >= endPtr)
|
||||
return 0;
|
||||
return *currentPtr;
|
||||
}
|
||||
|
||||
// Read short.
|
||||
if (itemSize == 2)
|
||||
{
|
||||
if (currentPtr + 2 >= endPtr)
|
||||
return 0;
|
||||
var data1 = *currentPtr;
|
||||
var data2 = *(currentPtr + 1);
|
||||
return (data2 << 8) | data1;
|
||||
}
|
||||
|
||||
// Read int.
|
||||
if (itemSize == 3) // Item size 3 means 4 bytes!
|
||||
{
|
||||
if (currentPtr + 4 >= endPtr)
|
||||
return 0;
|
||||
|
||||
var data1 = *currentPtr;
|
||||
var data2 = *(currentPtr + 1);
|
||||
var data3 = *(currentPtr + 2);
|
||||
var data4 = *(currentPtr + 3);
|
||||
|
||||
return (data4 << 24) | (data3 << 24) | (data2 << 8) | data1;
|
||||
}
|
||||
|
||||
Debug.Assert(false, "Should not reach here");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private struct HIDReportData
|
||||
{
|
||||
public int reportId;
|
||||
public HID.HIDReportType reportType;
|
||||
public int currentBitOffset;
|
||||
|
||||
public static int FindOrAddReport(int? reportId, HID.HIDReportType reportType, List<HIDReportData> reports)
|
||||
{
|
||||
var id = 1;
|
||||
if (reportId.HasValue)
|
||||
id = reportId.Value;
|
||||
|
||||
for (var i = 0; i < reports.Count; ++i)
|
||||
{
|
||||
if (reports[i].reportId == id && reports[i].reportType == reportType)
|
||||
return i;
|
||||
}
|
||||
|
||||
reports.Add(new HIDReportData
|
||||
{
|
||||
reportId = id,
|
||||
reportType = reportType
|
||||
});
|
||||
|
||||
return reports.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// All types and tags with size bits (low order two bits) masked out (i.e. being 0).
|
||||
private enum HIDItemTypeAndTag
|
||||
{
|
||||
Input = 0x80,
|
||||
Output = 0x90,
|
||||
Feature = 0xB0,
|
||||
Collection = 0xA0,
|
||||
EndCollection = 0xC0,
|
||||
UsagePage = 0x04,
|
||||
LogicalMinimum = 0x14,
|
||||
LogicalMaximum = 0x24,
|
||||
PhysicalMinimum = 0x34,
|
||||
PhysicalMaximum = 0x44,
|
||||
UnitExponent = 0x54,
|
||||
Unit = 0x64,
|
||||
ReportSize = 0x74,
|
||||
ReportID = 0x84,
|
||||
ReportCount = 0x94,
|
||||
Push = 0xA4,
|
||||
Pop = 0xB4,
|
||||
Usage = 0x08,
|
||||
UsageMinimum = 0x18,
|
||||
UsageMaximum = 0x28,
|
||||
DesignatorIndex = 0x38,
|
||||
DesignatorMinimum = 0x48,
|
||||
DesignatorMaximum = 0x58,
|
||||
StringIndex = 0x78,
|
||||
StringMinimum = 0x88,
|
||||
StringMaximum = 0x98,
|
||||
Delimiter = 0xA8,
|
||||
}
|
||||
|
||||
// State that needs to be defined for each main item separately.
|
||||
// See section 6.2.2.8
|
||||
private struct HIDItemStateLocal
|
||||
{
|
||||
public int? usage;
|
||||
public int? usageMinimum;
|
||||
public int? usageMaximum;
|
||||
public int? designatorIndex;
|
||||
public int? designatorMinimum;
|
||||
public int? designatorMaximum;
|
||||
public int? stringIndex;
|
||||
public int? stringMinimum;
|
||||
public int? stringMaximum;
|
||||
|
||||
public List<int> usageList;
|
||||
|
||||
// Wipe state but preserve usageList allocation.
|
||||
public static void Reset(ref HIDItemStateLocal state)
|
||||
{
|
||||
var usageList = state.usageList;
|
||||
state = new HIDItemStateLocal();
|
||||
if (usageList != null)
|
||||
{
|
||||
usageList.Clear();
|
||||
state.usageList = usageList;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage can be set repeatedly to provide an enumeration of usages.
|
||||
public void SetUsage(int value)
|
||||
{
|
||||
if (usage.HasValue)
|
||||
{
|
||||
if (usageList == null)
|
||||
usageList = new List<int>();
|
||||
usageList.Add(usage.Value);
|
||||
}
|
||||
usage = value;
|
||||
}
|
||||
|
||||
// Get usage for Nth element in [0-reportCount] list.
|
||||
public int GetUsage(int index)
|
||||
{
|
||||
// If we have minimum and maximum usage, interpolate between that.
|
||||
if (usageMinimum.HasValue && usageMaximum.HasValue)
|
||||
{
|
||||
var min = usageMinimum.Value;
|
||||
var max = usageMaximum.Value;
|
||||
|
||||
var range = max - min;
|
||||
if (range < 0)
|
||||
return 0;
|
||||
if (index >= range)
|
||||
return max;
|
||||
return min + index;
|
||||
}
|
||||
|
||||
// If we have a list of usages, index into that.
|
||||
if (usageList != null && usageList.Count > 0)
|
||||
{
|
||||
var usageCount = usageList.Count;
|
||||
if (index >= usageCount)
|
||||
return usage.Value;
|
||||
|
||||
return usageList[index];
|
||||
}
|
||||
|
||||
if (usage.HasValue)
|
||||
return usage.Value;
|
||||
|
||||
////TODO: min/max
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// State that is carried over from main item to main item.
|
||||
// See section 6.2.2.7
|
||||
private struct HIDItemStateGlobal
|
||||
{
|
||||
public int? usagePage;
|
||||
public int? logicalMinimum;
|
||||
public int? logicalMaximum;
|
||||
public int? physicalMinimum;
|
||||
public int? physicalMaximum;
|
||||
public int? unitExponent;
|
||||
public int? unit;
|
||||
public int? reportSize;
|
||||
public int? reportCount;
|
||||
public int? reportId;
|
||||
|
||||
public HID.UsagePage GetUsagePage(int index, ref HIDItemStateLocal localItemState)
|
||||
{
|
||||
if (!usagePage.HasValue)
|
||||
{
|
||||
var usage = localItemState.GetUsage(index);
|
||||
return (HID.UsagePage)(usage >> 16);
|
||||
}
|
||||
|
||||
return (HID.UsagePage)usagePage.Value;
|
||||
}
|
||||
|
||||
public int GetPhysicalMin()
|
||||
{
|
||||
if (physicalMinimum == null || physicalMaximum == null ||
|
||||
(physicalMinimum.Value == 0 && physicalMaximum.Value == 0))
|
||||
return logicalMinimum.GetValueOrDefault(0);
|
||||
return physicalMinimum.Value;
|
||||
}
|
||||
|
||||
public int GetPhysicalMax()
|
||||
{
|
||||
if (physicalMinimum == null || physicalMaximum == null ||
|
||||
(physicalMinimum.Value == 0 && physicalMaximum.Value == 0))
|
||||
return logicalMaximum.GetValueOrDefault(0);
|
||||
return physicalMaximum.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user