test
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8806f46699a44c39a6a66c5d07772871
|
||||
timeCreated: 1510872623
|
@@ -0,0 +1,233 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine.InputSystem.Layouts;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
////TODO: use two columns for treeview and separate name and value
|
||||
|
||||
namespace UnityEngine.InputSystem.HID.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A window that dumps a raw HID descriptor in a tree view.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not specific to InputDevices of type <see cref="HID"/> so that it can work with
|
||||
/// any <see cref="InputDevice"/> created for a device using the "HID" interface.
|
||||
/// </remarks>
|
||||
internal class HIDDescriptorWindow : EditorWindow, ISerializationCallbackReceiver
|
||||
{
|
||||
public static void CreateOrShowExisting(int deviceId, InputDeviceDescription deviceDescription)
|
||||
{
|
||||
// See if we have an existing window for the device and if so pop it
|
||||
// in front.
|
||||
if (s_OpenWindows != null)
|
||||
{
|
||||
for (var i = 0; i < s_OpenWindows.Count; ++i)
|
||||
{
|
||||
var existingWindow = s_OpenWindows[i];
|
||||
if (existingWindow.m_DeviceId == deviceId)
|
||||
{
|
||||
existingWindow.Show();
|
||||
existingWindow.Focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No, so create a new one.
|
||||
var window = CreateInstance<HIDDescriptorWindow>();
|
||||
window.InitializeWith(deviceId, deviceDescription);
|
||||
window.minSize = new Vector2(270, 200);
|
||||
window.Show();
|
||||
window.titleContent = new GUIContent("HID Descriptor");
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
AddToList();
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
RemoveFromList();
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (!m_Initialized)
|
||||
InitializeWith(m_DeviceId, m_DeviceDescription);
|
||||
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
GUILayout.Label(m_Label, GUILayout.MinWidth(100), GUILayout.ExpandWidth(true));
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var rect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
|
||||
m_TreeView.OnGUI(rect);
|
||||
}
|
||||
|
||||
private void InitializeWith(int deviceId, InputDeviceDescription deviceDescription)
|
||||
{
|
||||
m_DeviceId = deviceId;
|
||||
m_DeviceDescription = deviceDescription;
|
||||
m_Initialized = true;
|
||||
|
||||
// Set up tree view for HID descriptor.
|
||||
var hidDescriptor = HID.ReadHIDDeviceDescriptor(ref m_DeviceDescription,
|
||||
(ref InputDeviceCommand command) => InputRuntime.s_Instance.DeviceCommand(m_DeviceId, ref command));
|
||||
if (m_TreeViewState == null)
|
||||
m_TreeViewState = new TreeViewState();
|
||||
m_TreeView = new HIDDescriptorTreeView(m_TreeViewState, hidDescriptor);
|
||||
m_TreeView.SetExpanded(1, true);
|
||||
|
||||
m_Label = new GUIContent(
|
||||
$"HID Descriptor for '{deviceDescription.manufacturer} {deviceDescription.product}'");
|
||||
}
|
||||
|
||||
[NonSerialized] private bool m_Initialized;
|
||||
[NonSerialized] private HIDDescriptorTreeView m_TreeView;
|
||||
[NonSerialized] private GUIContent m_Label;
|
||||
|
||||
[SerializeField] private int m_DeviceId;
|
||||
[SerializeField] private InputDeviceDescription m_DeviceDescription;
|
||||
[SerializeField] private TreeViewState m_TreeViewState;
|
||||
|
||||
private void AddToList()
|
||||
{
|
||||
if (s_OpenWindows == null)
|
||||
s_OpenWindows = new List<HIDDescriptorWindow>();
|
||||
if (!s_OpenWindows.Contains(this))
|
||||
s_OpenWindows.Add(this);
|
||||
}
|
||||
|
||||
private void RemoveFromList()
|
||||
{
|
||||
if (s_OpenWindows != null)
|
||||
s_OpenWindows.Remove(this);
|
||||
}
|
||||
|
||||
private static List<HIDDescriptorWindow> s_OpenWindows;
|
||||
|
||||
private class HIDDescriptorTreeView : TreeView
|
||||
{
|
||||
private HID.HIDDeviceDescriptor m_Descriptor;
|
||||
|
||||
public HIDDescriptorTreeView(TreeViewState state, HID.HIDDeviceDescriptor descriptor)
|
||||
: base(state)
|
||||
{
|
||||
m_Descriptor = descriptor;
|
||||
Reload();
|
||||
}
|
||||
|
||||
protected override TreeViewItem BuildRoot()
|
||||
{
|
||||
var id = 0;
|
||||
|
||||
var root = new TreeViewItem
|
||||
{
|
||||
id = id++,
|
||||
depth = -1
|
||||
};
|
||||
|
||||
var item = BuildDeviceItem(m_Descriptor, ref id);
|
||||
root.AddChild(item);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private TreeViewItem BuildDeviceItem(HID.HIDDeviceDescriptor device, ref int id)
|
||||
{
|
||||
var item = new TreeViewItem
|
||||
{
|
||||
id = id++,
|
||||
depth = 0,
|
||||
displayName = "Device"
|
||||
};
|
||||
|
||||
AddChild(item, string.Format("Vendor ID: 0x{0:X}", device.vendorId), ref id);
|
||||
AddChild(item, string.Format("Product ID: 0x{0:X}", device.productId), ref id);
|
||||
AddChild(item, string.Format("Usage Page: 0x{0:X} ({1})", (uint)device.usagePage, device.usagePage), ref id);
|
||||
AddChild(item, string.Format("Usage: 0x{0:X}", device.usage), ref id);
|
||||
AddChild(item, "Input Report Size: " + device.inputReportSize, ref id);
|
||||
AddChild(item, "Output Report Size: " + device.outputReportSize, ref id);
|
||||
AddChild(item, "Feature Report Size: " + device.featureReportSize, ref id);
|
||||
|
||||
// Elements.
|
||||
if (device.elements != null)
|
||||
{
|
||||
var elementCount = device.elements.Length;
|
||||
var elements = AddChild(item, elementCount + " Elements", ref id);
|
||||
for (var i = 0; i < elementCount; ++i)
|
||||
BuildElementItem(i, elements, device.elements[i], ref id);
|
||||
}
|
||||
else
|
||||
AddChild(item, "0 Elements", ref id);
|
||||
|
||||
////TODO: collections
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private TreeViewItem BuildElementItem(int index, TreeViewItem parent, HID.HIDElementDescriptor element, ref int id)
|
||||
{
|
||||
var item = AddChild(parent, string.Format("Element {0} ({1})", index, element.reportType), ref id);
|
||||
|
||||
string usagePageString = HID.UsagePageToString(element.usagePage);
|
||||
string usageString = HID.UsageToString(element.usagePage, element.usage);
|
||||
|
||||
AddChild(item, string.Format("Usage Page: 0x{0:X} ({1})", (uint)element.usagePage, usagePageString), ref id);
|
||||
if (usageString != null)
|
||||
AddChild(item, string.Format("Usage: 0x{0:X} ({1})", element.usage, usageString), ref id);
|
||||
else
|
||||
AddChild(item, string.Format("Usage: 0x{0:X}", element.usage), ref id);
|
||||
|
||||
AddChild(item, "Report Type: " + element.reportType, ref id);
|
||||
AddChild(item, "Report ID: " + element.reportId, ref id);
|
||||
AddChild(item, "Report Size in Bits: " + element.reportSizeInBits, ref id);
|
||||
AddChild(item, "Report Bit Offset: " + element.reportOffsetInBits, ref id);
|
||||
AddChild(item, "Collection Index: " + element.collectionIndex, ref id);
|
||||
AddChild(item, string.Format("Unit: {0:X}", element.unit), ref id);
|
||||
AddChild(item, string.Format("Unit Exponent: {0:X}", element.unitExponent), ref id);
|
||||
AddChild(item, "Logical Min: " + element.logicalMin, ref id);
|
||||
AddChild(item, "Logical Max: " + element.logicalMax, ref id);
|
||||
AddChild(item, "Physical Min: " + element.physicalMin, ref id);
|
||||
AddChild(item, "Physical Max: " + element.physicalMax, ref id);
|
||||
AddChild(item, "Has Null State?: " + element.hasNullState, ref id);
|
||||
AddChild(item, "Has Preferred State?: " + element.hasPreferredState, ref id);
|
||||
AddChild(item, "Is Array?: " + element.isArray, ref id);
|
||||
AddChild(item, "Is Non-Linear?: " + element.isNonLinear, ref id);
|
||||
AddChild(item, "Is Relative?: " + element.isRelative, ref id);
|
||||
AddChild(item, "Is Constant?: " + element.isConstant, ref id);
|
||||
AddChild(item, "Is Wrapping?: " + element.isWrapping, ref id);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private TreeViewItem AddChild(TreeViewItem parent, string displayName, ref int id)
|
||||
{
|
||||
var item = new TreeViewItem
|
||||
{
|
||||
id = id++,
|
||||
depth = parent.depth + 1,
|
||||
displayName = displayName
|
||||
};
|
||||
|
||||
parent.AddChild(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
AddToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae42a3986cf84fe2aa1df4aa984dd806
|
||||
timeCreated: 1513554255
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a736049645e8c4781aedf679c2ee6be6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,154 @@
|
||||
using System.Linq;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine.InputSystem.Editor;
|
||||
using UnityEngine.InputSystem.HID.Editor;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.InputSystem.HID
|
||||
{
|
||||
using ShouldCreateHIDCallback = System.Func<HID.HIDDeviceDescriptor, bool?>;
|
||||
|
||||
/// <summary>
|
||||
/// Adds support for generic HID devices to the input system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Even without this module, HIDs can be used on platforms where we
|
||||
/// support HID has a native backend (Windows and OSX, at the moment).
|
||||
/// However, each supported HID requires a layout specifically targeting
|
||||
/// it as a product.
|
||||
///
|
||||
/// What this module adds is the ability to turn any HID with usable
|
||||
/// controls into an InputDevice. It will make a best effort to figure
|
||||
/// out a suitable class for the device and will use the HID elements
|
||||
/// present in the HID report descriptor to populate the device.
|
||||
///
|
||||
/// If there is an existing product-specific layout for a HID, it will
|
||||
/// take precedence and HIDSupport will leave the device alone.
|
||||
/// </remarks>
|
||||
public static class HIDSupport
|
||||
{
|
||||
/// <summary>
|
||||
/// A pair of HID usage page and HID usage number.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used to describe a HID usage for the <see cref="supportedHIDUsages"/> property.
|
||||
/// </remarks>
|
||||
public struct HIDPageUsage
|
||||
{
|
||||
/// <summary>
|
||||
/// The usage page.
|
||||
/// </summary>
|
||||
public HID.UsagePage page;
|
||||
|
||||
/// <summary>
|
||||
/// A number specifying the usage on the usage page.
|
||||
/// </summary>
|
||||
public int usage;
|
||||
|
||||
/// <summary>
|
||||
/// Create a HIDPageUsage struct by specifying a page and usage.
|
||||
/// </summary>
|
||||
public HIDPageUsage(HID.UsagePage page, int usage)
|
||||
{
|
||||
this.page = page;
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a HIDPageUsage struct from the GenericDesktop usage page by specifying the usage.
|
||||
/// </summary>
|
||||
public HIDPageUsage(HID.GenericDesktop usage)
|
||||
{
|
||||
page = HID.UsagePage.GenericDesktop;
|
||||
this.usage = (int)usage;
|
||||
}
|
||||
}
|
||||
|
||||
private static HIDPageUsage[] s_SupportedHIDUsages;
|
||||
|
||||
/// <summary>
|
||||
/// An array of HID usages the input is configured to support.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The input system will only create <see cref="InputDevice"/>s for HIDs with usages
|
||||
/// listed in this array. Any other HID will be ignored. This saves the input system from
|
||||
/// spending resources on creating layouts and devices for HIDs which are not supported or
|
||||
/// not usable for game input.
|
||||
///
|
||||
/// By default, this includes only <see cref="HID.GenericDesktop.Joystick"/>,
|
||||
/// <see cref="HID.GenericDesktop.Gamepad"/> and <see cref="HID.GenericDesktop.MultiAxisController"/>,
|
||||
/// but you can set this property to include any other HID usages.
|
||||
///
|
||||
/// Note that currently on macOS, the only HID usages which can be enabled are
|
||||
/// <see cref="HID.GenericDesktop.Joystick"/>, <see cref="HID.GenericDesktop.Gamepad"/>,
|
||||
/// <see cref="HID.GenericDesktop.MultiAxisController"/>, <see cref="HID.GenericDesktop.TabletPCControls"/>,
|
||||
/// and <see cref="HID.GenericDesktop.AssistiveControl"/>.
|
||||
/// </remarks>
|
||||
public static ReadOnlyArray<HIDPageUsage> supportedHIDUsages
|
||||
{
|
||||
get => s_SupportedHIDUsages;
|
||||
set
|
||||
{
|
||||
s_SupportedHIDUsages = value.ToArray();
|
||||
|
||||
// Add HIDs we now support.
|
||||
InputSystem.s_Manager.AddAvailableDevicesThatAreNowRecognized();
|
||||
|
||||
// Remove HIDs we no longer support.
|
||||
for (var i = 0; i < InputSystem.devices.Count; ++i)
|
||||
{
|
||||
var device = InputSystem.devices[i];
|
||||
if (device is HID hid && !s_SupportedHIDUsages.Contains(new HIDPageUsage(hid.hidDescriptor.usagePage, hid.hidDescriptor.usage)))
|
||||
{
|
||||
// Remove the entire generated layout. This will also remove all devices based on it.
|
||||
InputSystem.RemoveLayout(device.layout);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add support for generic HIDs to InputSystem.
|
||||
/// </summary>
|
||||
#if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static void Initialize()
|
||||
{
|
||||
s_SupportedHIDUsages = new[]
|
||||
{
|
||||
new HIDPageUsage(HID.GenericDesktop.Joystick),
|
||||
new HIDPageUsage(HID.GenericDesktop.Gamepad),
|
||||
new HIDPageUsage(HID.GenericDesktop.MultiAxisController),
|
||||
};
|
||||
|
||||
InputSystem.RegisterLayout<HID>();
|
||||
InputSystem.onFindLayoutForDevice += HID.OnFindLayoutForDevice;
|
||||
|
||||
// Add toolbar button to any devices using the "HID" interface. Opens
|
||||
// a windows to browse the HID descriptor of the device.
|
||||
#if UNITY_EDITOR
|
||||
InputDeviceDebuggerWindow.onToolbarGUI +=
|
||||
device =>
|
||||
{
|
||||
if (device.description.interfaceName == HID.kHIDInterface)
|
||||
{
|
||||
if (GUILayout.Button(s_HIDDescriptor, EditorStyles.toolbarButton))
|
||||
{
|
||||
HIDDescriptorWindow.CreateOrShowExisting(device.deviceId, device.description);
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static readonly GUIContent s_HIDDescriptor = new GUIContent("HID Descriptor");
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f0b0cfb7c484e43823c1a038c97a5f3
|
||||
timeCreated: 1511133984
|
Reference in New Issue
Block a user