No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

HID.cs 63KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Text;
  6. using UnityEngine.InputSystem.LowLevel;
  7. using UnityEngine.InputSystem.Utilities;
  8. using Unity.Collections.LowLevel.Unsafe;
  9. using UnityEngine.InputSystem.Layouts;
  10. using UnityEngine.Scripting;
  11. #if UNITY_2021_2_OR_NEWER
  12. using UnityEngine.Pool;
  13. #endif
  14. // HID support is currently broken in 32-bit Windows standalone players. Consider 32bit Windows players unsupported for now.
  15. #if UNITY_STANDALONE_WIN && !UNITY_64
  16. #warning The 32-bit Windows player is not currently supported by the Input System. HID input will not work in the player. Please use x86_64, if possible.
  17. #endif
  18. ////REVIEW: there will probably be lots of cases where the HID device creation process just needs a little tweaking; we should
  19. //// have better mechanism to do that without requiring to replace the entire process wholesale
  20. ////TODO: expose the layout builder so that other layout builders can use it for their own purposes
  21. ////REVIEW: how are we dealing with multiple different input reports on the same device?
  22. ////REVIEW: move the enums and structs out of here and into UnityEngine.InputSystem.HID? Or remove the "HID" name prefixes from them?
  23. ////TODO: add blacklist for devices we really don't want to use (like apple's internal trackpad)
  24. ////TODO: add a way to mark certain layouts (such as HID layouts) as fallbacks; ideally, affect the layout matching score
  25. ////TODO: enable this to handle devices that split their input into multiple reports
  26. #pragma warning disable CS0649, CS0219
  27. namespace UnityEngine.InputSystem.HID
  28. {
  29. /// <summary>
  30. /// A generic HID input device.
  31. /// </summary>
  32. /// <remarks>
  33. /// This class represents a best effort to mirror the control setup of a HID
  34. /// discovered in the system. It is used only as a fallback where we cannot
  35. /// match the device to a specific product we know of. Wherever possible we
  36. /// construct more specific device representations such as Gamepad.
  37. /// </remarks>
  38. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
  39. public class HID : InputDevice
  40. {
  41. internal const string kHIDInterface = "HID";
  42. internal const string kHIDNamespace = "HID";
  43. /// <summary>
  44. /// Command code for querying the HID report descriptor from a device.
  45. /// </summary>
  46. /// <seealso cref="InputDevice.ExecuteCommand{TCommand}"/>
  47. public static FourCC QueryHIDReportDescriptorDeviceCommandType { get { return new FourCC('H', 'I', 'D', 'D'); } }
  48. /// <summary>
  49. /// Command code for querying the HID report descriptor size in bytes from a device.
  50. /// </summary>
  51. /// <seealso cref="InputDevice.ExecuteCommand{TCommand}"/>
  52. public static FourCC QueryHIDReportDescriptorSizeDeviceCommandType { get { return new FourCC('H', 'I', 'D', 'S'); } }
  53. public static FourCC QueryHIDParsedReportDescriptorDeviceCommandType { get { return new FourCC('H', 'I', 'D', 'P'); } }
  54. /// <summary>
  55. /// The HID device descriptor as received from the system.
  56. /// </summary>
  57. public HIDDeviceDescriptor hidDescriptor
  58. {
  59. get
  60. {
  61. if (!m_HaveParsedHIDDescriptor)
  62. {
  63. if (!string.IsNullOrEmpty(description.capabilities))
  64. m_HIDDescriptor = JsonUtility.FromJson<HIDDeviceDescriptor>(description.capabilities);
  65. m_HaveParsedHIDDescriptor = true;
  66. }
  67. return m_HIDDescriptor;
  68. }
  69. }
  70. private bool m_HaveParsedHIDDescriptor;
  71. private HIDDeviceDescriptor m_HIDDescriptor;
  72. // This is the workhorse for figuring out fallback options for HIDs attached to the system.
  73. // If the system cannot find a more specific layout for a given HID, this method will try
  74. // to produce a layout builder on the fly based on the HID descriptor received from
  75. // the device.
  76. internal static string OnFindLayoutForDevice(ref InputDeviceDescription description, string matchedLayout,
  77. InputDeviceExecuteCommandDelegate executeDeviceCommand)
  78. {
  79. // If the system found a matching layout, there's nothing for us to do.
  80. if (!string.IsNullOrEmpty(matchedLayout))
  81. return null;
  82. // If the device isn't a HID, we're not interested.
  83. if (description.interfaceName != kHIDInterface)
  84. return null;
  85. // Read HID descriptor.
  86. var hidDeviceDescriptor = ReadHIDDeviceDescriptor(ref description, executeDeviceCommand);
  87. if (!HIDSupport.supportedHIDUsages.Contains(new HIDSupport.HIDPageUsage(hidDeviceDescriptor.usagePage, hidDeviceDescriptor.usage)))
  88. return null;
  89. // Determine if there's any usable elements on the device.
  90. var hasUsableElements = false;
  91. if (hidDeviceDescriptor.elements != null)
  92. {
  93. foreach (var element in hidDeviceDescriptor.elements)
  94. {
  95. if (element.IsUsableElement())
  96. {
  97. hasUsableElements = true;
  98. break;
  99. }
  100. }
  101. }
  102. // If not, there's nothing we can do with the device.
  103. if (!hasUsableElements)
  104. return null;
  105. ////TODO: we should be able to differentiate a HID joystick from other joysticks in bindings alone
  106. // Determine base layout.
  107. var baseType = typeof(HID);
  108. var baseLayout = "HID";
  109. if (hidDeviceDescriptor.usagePage == UsagePage.GenericDesktop)
  110. {
  111. if (hidDeviceDescriptor.usage == (int)GenericDesktop.Joystick || hidDeviceDescriptor.usage == (int)GenericDesktop.Gamepad)
  112. {
  113. baseLayout = "Joystick";
  114. baseType = typeof(Joystick);
  115. }
  116. }
  117. // A HID may implement the HID interface arbitrary many times, each time with a different
  118. // usage page + usage combination. In a OS, this will typically come out as multiple separate
  119. // devices. Thus, to make layout names unique, we have to take usages into account. What we do
  120. // is we tag the usage name onto the layout name *except* if it's a joystick or gamepad. This
  121. // gives us nicer names for joysticks while still disambiguating other devices correctly.
  122. var usageName = "";
  123. if (baseLayout != "Joystick")
  124. {
  125. usageName = hidDeviceDescriptor.usagePage == UsagePage.GenericDesktop
  126. ? $" {(GenericDesktop) hidDeviceDescriptor.usage}"
  127. : $" {hidDeviceDescriptor.usagePage}-{hidDeviceDescriptor.usage}";
  128. }
  129. ////REVIEW: these layout names are impossible to bind to; come up with a better way
  130. ////TODO: match HID layouts by vendor and product ID
  131. ////REVIEW: this probably works fine for most products out there but I'm not sure it works reliably for all cases
  132. // Come up with a unique template name. HIDs are required to have product and vendor IDs.
  133. // We go with the string versions if we have them and with the numeric versions if we don't.
  134. string layoutName;
  135. var deviceMatcher = InputDeviceMatcher.FromDeviceDescription(description);
  136. if (!string.IsNullOrEmpty(description.product) && !string.IsNullOrEmpty(description.manufacturer))
  137. {
  138. layoutName = $"{kHIDNamespace}::{description.manufacturer} {description.product}{usageName}";
  139. }
  140. else if (!string.IsNullOrEmpty(description.product))
  141. {
  142. layoutName = $"{kHIDNamespace}::{description.product}{usageName}";
  143. }
  144. else
  145. {
  146. // Sanity check to make sure we really have the data we expect.
  147. if (hidDeviceDescriptor.vendorId == 0)
  148. return null;
  149. layoutName =
  150. $"{kHIDNamespace}::{hidDeviceDescriptor.vendorId:X}-{hidDeviceDescriptor.productId:X}{usageName}";
  151. deviceMatcher = deviceMatcher
  152. .WithCapability("productId", hidDeviceDescriptor.productId)
  153. .WithCapability("vendorId", hidDeviceDescriptor.vendorId);
  154. }
  155. // Also match by usage. See comment above about multiple HID interfaces on the same device.
  156. deviceMatcher = deviceMatcher
  157. .WithCapability("usage", hidDeviceDescriptor.usage)
  158. .WithCapability("usagePage", hidDeviceDescriptor.usagePage);
  159. // Register layout builder that will turn the HID descriptor into an
  160. // InputControlLayout instance.
  161. var layout = new HIDLayoutBuilder
  162. {
  163. displayName = description.product,
  164. hidDescriptor = hidDeviceDescriptor,
  165. parentLayout = baseLayout,
  166. deviceType = baseType ?? typeof(HID)
  167. };
  168. InputSystem.RegisterLayoutBuilder(() => layout.Build(),
  169. layoutName, baseLayout, deviceMatcher);
  170. return layoutName;
  171. }
  172. internal static unsafe HIDDeviceDescriptor ReadHIDDeviceDescriptor(ref InputDeviceDescription deviceDescription,
  173. InputDeviceExecuteCommandDelegate executeCommandDelegate)
  174. {
  175. if (deviceDescription.interfaceName != kHIDInterface)
  176. throw new ArgumentException(
  177. $"Device '{deviceDescription}' is not a HID");
  178. // See if we have to request a HID descriptor from the device.
  179. // We support having the descriptor directly as a JSON string in the `capabilities`
  180. // field of the device description.
  181. var needToRequestDescriptor = true;
  182. var hidDeviceDescriptor = new HIDDeviceDescriptor();
  183. if (!string.IsNullOrEmpty(deviceDescription.capabilities))
  184. {
  185. try
  186. {
  187. hidDeviceDescriptor = HIDDeviceDescriptor.FromJson(deviceDescription.capabilities);
  188. // If there's elements in the descriptor, we're good with the descriptor. If there aren't,
  189. // we go and ask the device for a full descriptor.
  190. if (hidDeviceDescriptor.elements != null && hidDeviceDescriptor.elements.Length > 0)
  191. needToRequestDescriptor = false;
  192. }
  193. catch (Exception exception)
  194. {
  195. Debug.LogError($"Could not parse HID descriptor of device '{deviceDescription}'");
  196. Debug.LogException(exception);
  197. }
  198. }
  199. ////REVIEW: we *could* switch to a single path here that supports *only* parsed descriptors but it'd
  200. //// mean having to switch *every* platform supporting HID to the hack we currently have to do
  201. //// on Windows
  202. // Request descriptor, if necessary.
  203. if (needToRequestDescriptor)
  204. {
  205. // Try to get the size of the HID descriptor from the device.
  206. var sizeOfDescriptorCommand = new InputDeviceCommand(QueryHIDReportDescriptorSizeDeviceCommandType);
  207. var sizeOfDescriptorInBytes = executeCommandDelegate(ref sizeOfDescriptorCommand);
  208. if (sizeOfDescriptorInBytes > 0)
  209. {
  210. // Now try to fetch the HID descriptor.
  211. using (var buffer =
  212. InputDeviceCommand.AllocateNative(QueryHIDReportDescriptorDeviceCommandType, (int)sizeOfDescriptorInBytes))
  213. {
  214. var commandPtr = (InputDeviceCommand*)buffer.GetUnsafePtr();
  215. if (executeCommandDelegate(ref *commandPtr) != sizeOfDescriptorInBytes)
  216. return new HIDDeviceDescriptor();
  217. // Try to parse the HID report descriptor.
  218. if (!HIDParser.ParseReportDescriptor((byte*)commandPtr->payloadPtr, (int)sizeOfDescriptorInBytes, ref hidDeviceDescriptor))
  219. return new HIDDeviceDescriptor();
  220. }
  221. // Update the descriptor on the device with the information we got.
  222. deviceDescription.capabilities = hidDeviceDescriptor.ToJson();
  223. }
  224. else
  225. {
  226. // The device may not support binary descriptors but may support parsed descriptors so
  227. // try the IOCTL for parsed descriptors next.
  228. //
  229. // This path exists pretty much only for the sake of Windows where it is not possible to get
  230. // unparsed/binary descriptors from the device (and where getting element offsets is only possible
  231. // with some dirty hacks we're performing in the native runtime).
  232. const int kMaxDescriptorBufferSize = 2 * 1024 * 1024; ////TODO: switch to larger buffer based on return code if request fails
  233. using (var buffer =
  234. InputDeviceCommand.AllocateNative(QueryHIDParsedReportDescriptorDeviceCommandType, kMaxDescriptorBufferSize))
  235. {
  236. var commandPtr = (InputDeviceCommand*)buffer.GetUnsafePtr();
  237. var utf8Length = executeCommandDelegate(ref *commandPtr);
  238. if (utf8Length < 0)
  239. return new HIDDeviceDescriptor();
  240. // Turn UTF-8 buffer into string.
  241. ////TODO: is there a way to not have to copy here?
  242. var utf8 = new byte[utf8Length];
  243. fixed(byte* utf8Ptr = utf8)
  244. {
  245. UnsafeUtility.MemCpy(utf8Ptr, commandPtr->payloadPtr, utf8Length);
  246. }
  247. var descriptorJson = Encoding.UTF8.GetString(utf8, 0, (int)utf8Length);
  248. // Try to parse the HID report descriptor.
  249. try
  250. {
  251. hidDeviceDescriptor = HIDDeviceDescriptor.FromJson(descriptorJson);
  252. }
  253. catch (Exception exception)
  254. {
  255. Debug.LogError($"Could not parse HID descriptor of device '{deviceDescription}'");
  256. Debug.LogException(exception);
  257. return new HIDDeviceDescriptor();
  258. }
  259. // Update the descriptor on the device with the information we got.
  260. deviceDescription.capabilities = descriptorJson;
  261. }
  262. }
  263. }
  264. return hidDeviceDescriptor;
  265. }
  266. public static string UsagePageToString(UsagePage usagePage)
  267. {
  268. return (int)usagePage >= 0xFF00 ? "Vendor-Defined" : usagePage.ToString();
  269. }
  270. public static string UsageToString(UsagePage usagePage, int usage)
  271. {
  272. switch (usagePage)
  273. {
  274. case UsagePage.GenericDesktop:
  275. return ((GenericDesktop)usage).ToString();
  276. case UsagePage.Simulation:
  277. return ((Simulation)usage).ToString();
  278. default:
  279. return null;
  280. }
  281. }
  282. [Serializable]
  283. private class HIDLayoutBuilder
  284. {
  285. public string displayName;
  286. public HIDDeviceDescriptor hidDescriptor;
  287. public string parentLayout;
  288. public Type deviceType;
  289. public InputControlLayout Build()
  290. {
  291. var builder = new InputControlLayout.Builder
  292. {
  293. displayName = displayName,
  294. type = deviceType,
  295. extendsLayout = parentLayout,
  296. stateFormat = new FourCC('H', 'I', 'D')
  297. };
  298. var xElement = Array.Find(hidDescriptor.elements,
  299. element => element.usagePage == UsagePage.GenericDesktop &&
  300. element.usage == (int)GenericDesktop.X);
  301. var yElement = Array.Find(hidDescriptor.elements,
  302. element => element.usagePage == UsagePage.GenericDesktop &&
  303. element.usage == (int)GenericDesktop.Y);
  304. ////REVIEW: in case the X and Y control are non-contiguous, should we even turn them into a stick
  305. ////REVIEW: there *has* to be an X and a Y for us to be able to successfully create a joystick
  306. // If GenericDesktop.X and GenericDesktop.Y are both present, turn the controls
  307. // into a stick.
  308. var haveStick = xElement.usage == (int)GenericDesktop.X && yElement.usage == (int)GenericDesktop.Y;
  309. if (haveStick)
  310. {
  311. int bitOffset, byteOffset, sizeInBits;
  312. if (xElement.reportOffsetInBits <= yElement.reportOffsetInBits)
  313. {
  314. bitOffset = xElement.reportOffsetInBits % 8;
  315. byteOffset = xElement.reportOffsetInBits / 8;
  316. sizeInBits = (yElement.reportOffsetInBits + yElement.reportSizeInBits) -
  317. xElement.reportOffsetInBits;
  318. }
  319. else
  320. {
  321. bitOffset = yElement.reportOffsetInBits % 8;
  322. byteOffset = yElement.reportOffsetInBits / 8;
  323. sizeInBits = (xElement.reportOffsetInBits + xElement.reportSizeInBits) -
  324. yElement.reportSizeInBits;
  325. }
  326. const string stickName = "stick";
  327. builder.AddControl(stickName)
  328. .WithDisplayName("Stick")
  329. .WithLayout("Stick")
  330. .WithBitOffset((uint)bitOffset)
  331. .WithByteOffset((uint)byteOffset)
  332. .WithSizeInBits((uint)sizeInBits)
  333. .WithUsages(CommonUsages.Primary2DMotion);
  334. var xElementParameters = xElement.DetermineParameters();
  335. var yElementParameters = yElement.DetermineParameters();
  336. builder.AddControl(stickName + "/x")
  337. .WithFormat(xElement.isSigned ? InputStateBlock.FormatSBit : InputStateBlock.FormatBit)
  338. .WithByteOffset((uint)(xElement.reportOffsetInBits / 8 - byteOffset))
  339. .WithBitOffset((uint)(xElement.reportOffsetInBits % 8))
  340. .WithSizeInBits((uint)xElement.reportSizeInBits)
  341. .WithParameters(xElementParameters)
  342. .WithDefaultState(xElement.DetermineDefaultState())
  343. .WithProcessors(xElement.DetermineProcessors());
  344. builder.AddControl(stickName + "/y")
  345. .WithFormat(yElement.isSigned ? InputStateBlock.FormatSBit : InputStateBlock.FormatBit)
  346. .WithByteOffset((uint)(yElement.reportOffsetInBits / 8 - byteOffset))
  347. .WithBitOffset((uint)(yElement.reportOffsetInBits % 8))
  348. .WithSizeInBits((uint)yElement.reportSizeInBits)
  349. .WithParameters(yElementParameters)
  350. .WithDefaultState(yElement.DetermineDefaultState())
  351. .WithProcessors(yElement.DetermineProcessors());
  352. // Propagate parameters needed on x and y to the four button controls.
  353. builder.AddControl(stickName + "/up")
  354. .WithParameters(
  355. StringHelpers.Join(",", yElementParameters, "clamp=2,clampMin=-1,clampMax=0,invert=true"));
  356. builder.AddControl(stickName + "/down")
  357. .WithParameters(
  358. StringHelpers.Join(",", yElementParameters, "clamp=2,clampMin=0,clampMax=1,invert=false"));
  359. builder.AddControl(stickName + "/left")
  360. .WithParameters(
  361. StringHelpers.Join(",", xElementParameters, "clamp=2,clampMin=-1,clampMax=0,invert"));
  362. builder.AddControl(stickName + "/right")
  363. .WithParameters(
  364. StringHelpers.Join(",", xElementParameters, "clamp=2,clampMin=0,clampMax=1"));
  365. }
  366. // Process HID descriptor.
  367. var elements = hidDescriptor.elements;
  368. var elementCount = elements.Length;
  369. for (var i = 0; i < elementCount; ++i)
  370. {
  371. ref var element = ref elements[i];
  372. if (element.reportType != HIDReportType.Input)
  373. continue;
  374. // Skip X and Y if we already turned them into a stick.
  375. if (haveStick && (element.Is(UsagePage.GenericDesktop, (int)GenericDesktop.X) ||
  376. element.Is(UsagePage.GenericDesktop, (int)GenericDesktop.Y)))
  377. continue;
  378. var layout = element.DetermineLayout();
  379. if (layout != null)
  380. {
  381. // Assign unique name.
  382. var name = element.DetermineName();
  383. Debug.Assert(!string.IsNullOrEmpty(name));
  384. name = StringHelpers.MakeUniqueName(name, builder.controls, x => x.name);
  385. // Add control.
  386. var control =
  387. builder.AddControl(name)
  388. .WithDisplayName(element.DetermineDisplayName())
  389. .WithLayout(layout)
  390. .WithByteOffset((uint)element.reportOffsetInBits / 8)
  391. .WithBitOffset((uint)element.reportOffsetInBits % 8)
  392. .WithSizeInBits((uint)element.reportSizeInBits)
  393. .WithFormat(element.DetermineFormat())
  394. .WithDefaultState(element.DetermineDefaultState())
  395. .WithProcessors(element.DetermineProcessors());
  396. var parameters = element.DetermineParameters();
  397. if (!string.IsNullOrEmpty(parameters))
  398. control.WithParameters(parameters);
  399. var usages = element.DetermineUsages();
  400. if (usages != null)
  401. control.WithUsages(usages);
  402. element.AddChildControls(ref element, name, ref builder);
  403. }
  404. }
  405. return builder.Build();
  406. }
  407. }
  408. public enum HIDReportType
  409. {
  410. Unknown,
  411. Input,
  412. Output,
  413. Feature
  414. }
  415. public enum HIDCollectionType
  416. {
  417. Physical = 0x00,
  418. Application = 0x01,
  419. Logical = 0x02,
  420. Report = 0x03,
  421. NamedArray = 0x04,
  422. UsageSwitch = 0x05,
  423. UsageModifier = 0x06
  424. }
  425. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags", Justification = "No better term for underlying data.")]
  426. [Flags]
  427. public enum HIDElementFlags
  428. {
  429. Constant = 1 << 0,
  430. Variable = 1 << 1,
  431. Relative = 1 << 2,
  432. Wrap = 1 << 3,
  433. NonLinear = 1 << 4,
  434. NoPreferred = 1 << 5,
  435. NullState = 1 << 6,
  436. Volatile = 1 << 7,
  437. BufferedBytes = 1 << 8
  438. }
  439. /// <summary>
  440. /// Descriptor for a single report element.
  441. /// </summary>
  442. [Serializable]
  443. public struct HIDElementDescriptor
  444. {
  445. public int usage;
  446. public UsagePage usagePage;
  447. public int unit;
  448. public int unitExponent;
  449. public int logicalMin;
  450. public int logicalMax;
  451. public int physicalMin;
  452. public int physicalMax;
  453. public HIDReportType reportType;
  454. public int collectionIndex;
  455. public int reportId;
  456. public int reportSizeInBits;
  457. public int reportOffsetInBits;
  458. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "flags", Justification = "No better term for underlying data.")]
  459. public HIDElementFlags flags;
  460. // Fields only relevant to arrays.
  461. public int? usageMin;
  462. public int? usageMax;
  463. public bool hasNullState => (flags & HIDElementFlags.NullState) == HIDElementFlags.NullState;
  464. public bool hasPreferredState => (flags & HIDElementFlags.NoPreferred) != HIDElementFlags.NoPreferred;
  465. public bool isArray => (flags & HIDElementFlags.Variable) != HIDElementFlags.Variable;
  466. public bool isNonLinear => (flags & HIDElementFlags.NonLinear) == HIDElementFlags.NonLinear;
  467. public bool isRelative => (flags & HIDElementFlags.Relative) == HIDElementFlags.Relative;
  468. public bool isConstant => (flags & HIDElementFlags.Constant) == HIDElementFlags.Constant;
  469. public bool isWrapping => (flags & HIDElementFlags.Wrap) == HIDElementFlags.Wrap;
  470. internal bool isSigned => logicalMin < 0;
  471. internal float minFloatValue
  472. {
  473. get
  474. {
  475. if (isSigned)
  476. {
  477. var minValue = (int)-(long)(1UL << (reportSizeInBits - 1));
  478. var maxValue = (int)((1UL << (reportSizeInBits - 1)) - 1);
  479. return NumberHelpers.IntToNormalizedFloat(logicalMin, minValue, maxValue) * 2.0f - 1.0f;
  480. }
  481. else
  482. {
  483. Debug.Assert(logicalMin >= 0, $"Expected logicalMin to be unsigned");
  484. var maxValue = (uint)((1UL << reportSizeInBits) - 1);
  485. return NumberHelpers.UIntToNormalizedFloat((uint)logicalMin, 0, maxValue);
  486. }
  487. }
  488. }
  489. internal float maxFloatValue
  490. {
  491. get
  492. {
  493. if (isSigned)
  494. {
  495. var minValue = (int)-(long)(1UL << (reportSizeInBits - 1));
  496. var maxValue = (int)((1UL << (reportSizeInBits - 1)) - 1);
  497. return NumberHelpers.IntToNormalizedFloat(logicalMax, minValue, maxValue) * 2.0f - 1.0f;
  498. }
  499. else
  500. {
  501. Debug.Assert(logicalMax >= 0, $"Expected logicalMax to be unsigned");
  502. var maxValue = (uint)((1UL << reportSizeInBits) - 1);
  503. return NumberHelpers.UIntToNormalizedFloat((uint)logicalMax, 0, maxValue);
  504. }
  505. }
  506. }
  507. public bool Is(UsagePage usagePage, int usage)
  508. {
  509. return usagePage == this.usagePage && usage == this.usage;
  510. }
  511. internal string DetermineName()
  512. {
  513. // It's rare for HIDs to declare string names for items and HID drivers may report weird strings
  514. // plus there's no guarantee that these names are unique per item. So, we don't bother here with
  515. // device/driver-supplied names at all but rather do our own naming.
  516. switch (usagePage)
  517. {
  518. case UsagePage.Button:
  519. if (usage == 1)
  520. return "trigger";
  521. return $"button{usage}";
  522. case UsagePage.GenericDesktop:
  523. if (usage == (int)GenericDesktop.HatSwitch)
  524. return "hat";
  525. var text = ((GenericDesktop)usage).ToString();
  526. // Lower-case first letter.
  527. text = char.ToLowerInvariant(text[0]) + text.Substring(1);
  528. return text;
  529. }
  530. // Fallback that generates a somewhat useless but at least very informative name.
  531. return $"UsagePage({usagePage:X}) Usage({usage:X})";
  532. }
  533. internal string DetermineDisplayName()
  534. {
  535. switch (usagePage)
  536. {
  537. case UsagePage.Button:
  538. if (usage == 1)
  539. return "Trigger";
  540. return $"Button {usage}";
  541. case UsagePage.GenericDesktop:
  542. return ((GenericDesktop)usage).ToString();
  543. }
  544. return null;
  545. }
  546. internal bool IsUsableElement()
  547. {
  548. switch (usage)
  549. {
  550. case (int)GenericDesktop.X:
  551. case (int)GenericDesktop.Y:
  552. return usagePage == UsagePage.GenericDesktop;
  553. default:
  554. return DetermineLayout() != null;
  555. }
  556. }
  557. internal string DetermineLayout()
  558. {
  559. if (reportType != HIDReportType.Input)
  560. return null;
  561. ////TODO: deal with arrays
  562. switch (usagePage)
  563. {
  564. case UsagePage.Button:
  565. return "Button";
  566. case UsagePage.GenericDesktop:
  567. switch (usage)
  568. {
  569. case (int)GenericDesktop.X:
  570. case (int)GenericDesktop.Y:
  571. case (int)GenericDesktop.Z:
  572. case (int)GenericDesktop.Rx:
  573. case (int)GenericDesktop.Ry:
  574. case (int)GenericDesktop.Rz:
  575. case (int)GenericDesktop.Vx:
  576. case (int)GenericDesktop.Vy:
  577. case (int)GenericDesktop.Vz:
  578. case (int)GenericDesktop.Vbrx:
  579. case (int)GenericDesktop.Vbry:
  580. case (int)GenericDesktop.Vbrz:
  581. case (int)GenericDesktop.Slider:
  582. case (int)GenericDesktop.Dial:
  583. case (int)GenericDesktop.Wheel:
  584. return "Axis";
  585. case (int)GenericDesktop.Select:
  586. case (int)GenericDesktop.Start:
  587. case (int)GenericDesktop.DpadUp:
  588. case (int)GenericDesktop.DpadDown:
  589. case (int)GenericDesktop.DpadLeft:
  590. case (int)GenericDesktop.DpadRight:
  591. return "Button";
  592. case (int)GenericDesktop.HatSwitch:
  593. // Only support hat switches with 8 directions.
  594. if (logicalMax - logicalMin + 1 == 8)
  595. return "Dpad";
  596. break;
  597. }
  598. break;
  599. }
  600. return null;
  601. }
  602. internal FourCC DetermineFormat()
  603. {
  604. switch (reportSizeInBits)
  605. {
  606. case 8:
  607. return isSigned ? InputStateBlock.FormatSByte : InputStateBlock.FormatByte;
  608. case 16:
  609. return isSigned ? InputStateBlock.FormatShort : InputStateBlock.FormatUShort;
  610. case 32:
  611. return isSigned ? InputStateBlock.FormatInt : InputStateBlock.FormatUInt;
  612. default:
  613. // Generic bitfield value.
  614. return InputStateBlock.FormatBit;
  615. }
  616. }
  617. internal InternedString[] DetermineUsages()
  618. {
  619. if (usagePage == UsagePage.Button && usage == 1)
  620. return new[] {CommonUsages.PrimaryTrigger, CommonUsages.PrimaryAction};
  621. if (usagePage == UsagePage.Button && usage == 2)
  622. return new[] {CommonUsages.SecondaryTrigger, CommonUsages.SecondaryAction};
  623. if (usagePage == UsagePage.GenericDesktop && usage == (int)GenericDesktop.Rz)
  624. return new[] { CommonUsages.Twist };
  625. ////TODO: assign hatswitch usage to first and only to first hatswitch element
  626. return null;
  627. }
  628. internal string DetermineParameters()
  629. {
  630. if (usagePage == UsagePage.GenericDesktop)
  631. {
  632. switch (usage)
  633. {
  634. case (int)GenericDesktop.X:
  635. case (int)GenericDesktop.Z:
  636. case (int)GenericDesktop.Rx:
  637. case (int)GenericDesktop.Rz:
  638. case (int)GenericDesktop.Vx:
  639. case (int)GenericDesktop.Vz:
  640. case (int)GenericDesktop.Vbrx:
  641. case (int)GenericDesktop.Vbrz:
  642. case (int)GenericDesktop.Slider:
  643. case (int)GenericDesktop.Dial:
  644. case (int)GenericDesktop.Wheel:
  645. return DetermineAxisNormalizationParameters();
  646. // Our Ys tend to be the opposite of what most HIDs do. We can't be sure and may well
  647. // end up inverting a value here when we shouldn't but as always with the HID fallback,
  648. // let's try to do what *seems* to work with the majority of devices.
  649. case (int)GenericDesktop.Y:
  650. case (int)GenericDesktop.Ry:
  651. case (int)GenericDesktop.Vy:
  652. case (int)GenericDesktop.Vbry:
  653. return StringHelpers.Join(",", "invert", DetermineAxisNormalizationParameters());
  654. }
  655. }
  656. return null;
  657. }
  658. private string DetermineAxisNormalizationParameters()
  659. {
  660. // If we have min/max bounds on the axis values, set up normalization on the axis.
  661. // NOTE: We put the center in the middle between min/max as we can't know where the
  662. // resting point of the axis is (may be on min if it's a trigger, for example).
  663. if (logicalMin == 0 && logicalMax == 0)
  664. return "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5";
  665. var min = minFloatValue;
  666. var max = maxFloatValue;
  667. // Do nothing if result of floating-point conversion is already normalized.
  668. if (Mathf.Approximately(0f, min) && Mathf.Approximately(0f, max))
  669. return null;
  670. var zero = min + (max - min) / 2.0f;
  671. return string.Format(CultureInfo.InvariantCulture, "normalize,normalizeMin={0},normalizeMax={1},normalizeZero={2}", min, max, zero);
  672. }
  673. internal string DetermineProcessors()
  674. {
  675. switch (usagePage)
  676. {
  677. case UsagePage.GenericDesktop:
  678. switch (usage)
  679. {
  680. case (int)GenericDesktop.X:
  681. case (int)GenericDesktop.Y:
  682. case (int)GenericDesktop.Z:
  683. case (int)GenericDesktop.Rx:
  684. case (int)GenericDesktop.Ry:
  685. case (int)GenericDesktop.Rz:
  686. case (int)GenericDesktop.Vx:
  687. case (int)GenericDesktop.Vy:
  688. case (int)GenericDesktop.Vz:
  689. case (int)GenericDesktop.Vbrx:
  690. case (int)GenericDesktop.Vbry:
  691. case (int)GenericDesktop.Vbrz:
  692. case (int)GenericDesktop.Slider:
  693. case (int)GenericDesktop.Dial:
  694. case (int)GenericDesktop.Wheel:
  695. return "axisDeadzone";
  696. }
  697. break;
  698. }
  699. return null;
  700. }
  701. internal PrimitiveValue DetermineDefaultState()
  702. {
  703. switch (usagePage)
  704. {
  705. case UsagePage.GenericDesktop:
  706. switch (usage)
  707. {
  708. case (int)GenericDesktop.HatSwitch:
  709. // Figure out null state for hat switches.
  710. if (hasNullState)
  711. {
  712. // We're looking for a value that is out-of-range with respect to the
  713. // logical min and max but in range with respect to what we can store
  714. // in the bits we have.
  715. // Test lower bound, we can store >= 0.
  716. if (logicalMin >= 1)
  717. return new PrimitiveValue(logicalMin - 1);
  718. // Test upper bound, we can store <= maxValue.
  719. var maxValue = (1UL << reportSizeInBits) - 1;
  720. if ((ulong)logicalMax < maxValue)
  721. return new PrimitiveValue(logicalMax + 1);
  722. }
  723. break;
  724. case (int)GenericDesktop.X:
  725. case (int)GenericDesktop.Y:
  726. case (int)GenericDesktop.Z:
  727. case (int)GenericDesktop.Rx:
  728. case (int)GenericDesktop.Ry:
  729. case (int)GenericDesktop.Rz:
  730. case (int)GenericDesktop.Vx:
  731. case (int)GenericDesktop.Vy:
  732. case (int)GenericDesktop.Vz:
  733. case (int)GenericDesktop.Vbrx:
  734. case (int)GenericDesktop.Vbry:
  735. case (int)GenericDesktop.Vbrz:
  736. case (int)GenericDesktop.Slider:
  737. case (int)GenericDesktop.Dial:
  738. case (int)GenericDesktop.Wheel:
  739. // For axes that are *NOT* stored as signed values (which we assume are
  740. // centered on 0), put the default state in the middle between the min and max.
  741. if (!isSigned)
  742. {
  743. var defaultValue = logicalMin + (logicalMax - logicalMin) / 2;
  744. if (defaultValue != 0)
  745. return new PrimitiveValue(defaultValue);
  746. }
  747. break;
  748. }
  749. break;
  750. }
  751. return new PrimitiveValue();
  752. }
  753. internal void AddChildControls(ref HIDElementDescriptor element, string controlName, ref InputControlLayout.Builder builder)
  754. {
  755. if (usagePage == UsagePage.GenericDesktop && usage == (int)GenericDesktop.HatSwitch)
  756. {
  757. // There doesn't seem to be enough specificity in the HID spec to reliably figure this case out.
  758. // Albeit detail is scarce, we could probably make some inferences based on the unit setting
  759. // of the hat switch but even then it seems there's much left to the whims of a hardware manufacturer.
  760. // Even if we know values go clockwise (HID spec doesn't really say; probably can be inferred from unit),
  761. // which direction do we start with? Is 0 degrees up or right?
  762. //
  763. // What we do here is simply make the assumption that we're dealing with degrees here, that we go clockwise,
  764. // and that 0 degrees is up (which is actually the opposite of the coordinate system suggested in 5.9 of
  765. // of the HID spec but seems to be what manufacturers are actually using in practice). Of course, if the
  766. // device we're looking at actually sets things up differently, then we end up with either an incorrectly
  767. // oriented or (worse) a non-functional hat switch.
  768. var nullValue = DetermineDefaultState();
  769. if (nullValue.isEmpty)
  770. return;
  771. ////REVIEW: this probably only works with hatswitches that have their null value at logicalMax+1
  772. builder.AddControl(controlName + "/up")
  773. .WithFormat(InputStateBlock.FormatBit)
  774. .WithLayout("DiscreteButton")
  775. .WithParameters(string.Format(CultureInfo.InvariantCulture,
  776. "minValue={0},maxValue={1},nullValue={2},wrapAtValue={3}",
  777. logicalMax, logicalMin + 1, nullValue.ToString(), logicalMax))
  778. .WithBitOffset((uint)element.reportOffsetInBits % 8)
  779. .WithSizeInBits((uint)reportSizeInBits);
  780. builder.AddControl(controlName + "/right")
  781. .WithFormat(InputStateBlock.FormatBit)
  782. .WithLayout("DiscreteButton")
  783. .WithParameters(string.Format(CultureInfo.InvariantCulture,
  784. "minValue={0},maxValue={1}",
  785. logicalMin + 1, logicalMin + 3))
  786. .WithBitOffset((uint)element.reportOffsetInBits % 8)
  787. .WithSizeInBits((uint)reportSizeInBits);
  788. builder.AddControl(controlName + "/down")
  789. .WithFormat(InputStateBlock.FormatBit)
  790. .WithLayout("DiscreteButton")
  791. .WithParameters(string.Format(CultureInfo.InvariantCulture,
  792. "minValue={0},maxValue={1}",
  793. logicalMin + 3, logicalMin + 5))
  794. .WithBitOffset((uint)element.reportOffsetInBits % 8)
  795. .WithSizeInBits((uint)reportSizeInBits);
  796. builder.AddControl(controlName + "/left")
  797. .WithFormat(InputStateBlock.FormatBit)
  798. .WithLayout("DiscreteButton")
  799. .WithParameters(string.Format(CultureInfo.InvariantCulture,
  800. "minValue={0},maxValue={1}",
  801. logicalMin + 5, logicalMin + 7))
  802. .WithBitOffset((uint)element.reportOffsetInBits % 8)
  803. .WithSizeInBits((uint)reportSizeInBits);
  804. }
  805. }
  806. }
  807. /// <summary>
  808. /// Descriptor for a collection of HID elements.
  809. /// </summary>
  810. [Serializable]
  811. public struct HIDCollectionDescriptor
  812. {
  813. public HIDCollectionType type;
  814. public int usage;
  815. public UsagePage usagePage;
  816. public int parent; // -1 if no parent.
  817. public int childCount;
  818. public int firstChild;
  819. }
  820. /// <summary>
  821. /// HID descriptor for a HID class device.
  822. /// </summary>
  823. /// <remarks>
  824. /// This is a processed view of the combined descriptors provided by a HID as defined
  825. /// in the HID specification, i.e. it's a combination of information from the USB device
  826. /// descriptor, HID class descriptor, and HID report descriptor.
  827. /// </remarks>
  828. [Serializable]
  829. public struct HIDDeviceDescriptor
  830. {
  831. /// <summary>
  832. /// USB vendor ID.
  833. /// </summary>
  834. /// <remarks>
  835. /// To get the string version of the vendor ID, see <see cref="InputDeviceDescription.manufacturer"/>
  836. /// on <see cref="InputDevice.description"/>.
  837. /// </remarks>
  838. public int vendorId;
  839. /// <summary>
  840. /// USB product ID.
  841. /// </summary>
  842. public int productId;
  843. public int usage;
  844. public UsagePage usagePage;
  845. /// <summary>
  846. /// Maximum size of individual input reports sent by the device.
  847. /// </summary>
  848. public int inputReportSize;
  849. /// <summary>
  850. /// Maximum size of individual output reports sent to the device.
  851. /// </summary>
  852. public int outputReportSize;
  853. /// <summary>
  854. /// Maximum size of individual feature reports exchanged with the device.
  855. /// </summary>
  856. public int featureReportSize;
  857. public HIDElementDescriptor[] elements;
  858. public HIDCollectionDescriptor[] collections;
  859. public string ToJson()
  860. {
  861. return JsonUtility.ToJson(this, true);
  862. }
  863. public static HIDDeviceDescriptor FromJson(string json)
  864. {
  865. #if UNITY_2021_2_OR_NEWER
  866. try
  867. {
  868. // HID descriptors, when formatted correctly, are always json strings with no whitespace and a
  869. // predictable order of elements, so we can try and use this simple predictive parser to extract
  870. // the data. If for any reason the data is not formatted correctly, we'll automatically fall back
  871. // to Unity's default json parser.
  872. var descriptor = new HIDDeviceDescriptor();
  873. var jsonSpan = json.AsSpan();
  874. var parser = new PredictiveParser();
  875. parser.ExpectSingleChar(jsonSpan, '{');
  876. parser.AcceptString(jsonSpan, out _);
  877. parser.ExpectSingleChar(jsonSpan, ':');
  878. descriptor.vendorId = parser.ExpectInt(jsonSpan);
  879. parser.AcceptSingleChar(jsonSpan, ',');
  880. parser.AcceptString(jsonSpan, out _);
  881. parser.ExpectSingleChar(jsonSpan, ':');
  882. descriptor.productId = parser.ExpectInt(jsonSpan);
  883. parser.AcceptSingleChar(jsonSpan, ',');
  884. parser.AcceptString(jsonSpan, out _);
  885. parser.ExpectSingleChar(jsonSpan, ':');
  886. descriptor.usage = parser.ExpectInt(jsonSpan);
  887. parser.AcceptSingleChar(jsonSpan, ',');
  888. parser.AcceptString(jsonSpan, out _);
  889. parser.ExpectSingleChar(jsonSpan, ':');
  890. descriptor.usagePage = (UsagePage)parser.ExpectInt(jsonSpan);
  891. parser.AcceptSingleChar(jsonSpan, ',');
  892. parser.AcceptString(jsonSpan, out _);
  893. parser.ExpectSingleChar(jsonSpan, ':');
  894. descriptor.inputReportSize = parser.ExpectInt(jsonSpan);
  895. parser.AcceptSingleChar(jsonSpan, ',');
  896. parser.AcceptString(jsonSpan, out _);
  897. parser.ExpectSingleChar(jsonSpan, ':');
  898. descriptor.outputReportSize = parser.ExpectInt(jsonSpan);
  899. parser.AcceptSingleChar(jsonSpan, ',');
  900. parser.AcceptString(jsonSpan, out _);
  901. parser.ExpectSingleChar(jsonSpan, ':');
  902. descriptor.featureReportSize = parser.ExpectInt(jsonSpan);
  903. parser.AcceptSingleChar(jsonSpan, ',');
  904. // elements
  905. parser.AcceptString(jsonSpan, out var key);
  906. if (key.ToString() != "elements") return descriptor;
  907. parser.ExpectSingleChar(jsonSpan, ':');
  908. parser.ExpectSingleChar(jsonSpan, '[');
  909. using var pool = ListPool<HIDElementDescriptor>.Get(out var elements);
  910. while (!parser.AcceptSingleChar(jsonSpan, ']'))
  911. {
  912. parser.AcceptSingleChar(jsonSpan, ',');
  913. parser.ExpectSingleChar(jsonSpan, '{');
  914. HIDElementDescriptor elementDesc = default;
  915. parser.AcceptSingleChar(jsonSpan, '}');
  916. parser.AcceptSingleChar(jsonSpan, ',');
  917. // usage
  918. parser.ExpectString(jsonSpan);
  919. parser.ExpectSingleChar(jsonSpan, ':');
  920. elementDesc.usage = parser.ExpectInt(jsonSpan);
  921. parser.AcceptSingleChar(jsonSpan, ',');
  922. parser.ExpectString(jsonSpan);
  923. parser.ExpectSingleChar(jsonSpan, ':');
  924. elementDesc.usagePage = (UsagePage)parser.ExpectInt(jsonSpan);
  925. parser.AcceptSingleChar(jsonSpan, ',');
  926. parser.ExpectString(jsonSpan);
  927. parser.ExpectSingleChar(jsonSpan, ':');
  928. elementDesc.unit = parser.ExpectInt(jsonSpan);
  929. parser.AcceptSingleChar(jsonSpan, ',');
  930. parser.ExpectString(jsonSpan);
  931. parser.ExpectSingleChar(jsonSpan, ':');
  932. elementDesc.unitExponent = parser.ExpectInt(jsonSpan);
  933. parser.AcceptSingleChar(jsonSpan, ',');
  934. parser.ExpectString(jsonSpan);
  935. parser.ExpectSingleChar(jsonSpan, ':');
  936. elementDesc.logicalMin = parser.ExpectInt(jsonSpan);
  937. parser.AcceptSingleChar(jsonSpan, ',');
  938. parser.ExpectString(jsonSpan);
  939. parser.ExpectSingleChar(jsonSpan, ':');
  940. elementDesc.logicalMax = parser.ExpectInt(jsonSpan);
  941. parser.AcceptSingleChar(jsonSpan, ',');
  942. parser.ExpectString(jsonSpan);
  943. parser.ExpectSingleChar(jsonSpan, ':');
  944. elementDesc.physicalMin = parser.ExpectInt(jsonSpan);
  945. parser.AcceptSingleChar(jsonSpan, ',');
  946. parser.ExpectString(jsonSpan);
  947. parser.ExpectSingleChar(jsonSpan, ':');
  948. elementDesc.physicalMax = parser.ExpectInt(jsonSpan);
  949. parser.AcceptSingleChar(jsonSpan, ',');
  950. parser.ExpectString(jsonSpan);
  951. parser.ExpectSingleChar(jsonSpan, ':');
  952. elementDesc.collectionIndex = parser.ExpectInt(jsonSpan);
  953. parser.AcceptSingleChar(jsonSpan, ',');
  954. parser.ExpectString(jsonSpan);
  955. parser.ExpectSingleChar(jsonSpan, ':');
  956. elementDesc.reportType = (HIDReportType)parser.ExpectInt(jsonSpan);
  957. parser.AcceptSingleChar(jsonSpan, ',');
  958. parser.ExpectString(jsonSpan);
  959. parser.ExpectSingleChar(jsonSpan, ':');
  960. elementDesc.reportId = parser.ExpectInt(jsonSpan);
  961. parser.AcceptSingleChar(jsonSpan, ',');
  962. // reportCount. We don't store this one
  963. parser.ExpectString(jsonSpan);
  964. parser.ExpectSingleChar(jsonSpan, ':');
  965. parser.AcceptInt(jsonSpan);
  966. parser.AcceptSingleChar(jsonSpan, ',');
  967. parser.ExpectString(jsonSpan);
  968. parser.ExpectSingleChar(jsonSpan, ':');
  969. elementDesc.reportSizeInBits = parser.ExpectInt(jsonSpan);
  970. parser.AcceptSingleChar(jsonSpan, ',');
  971. parser.ExpectString(jsonSpan);
  972. parser.ExpectSingleChar(jsonSpan, ':');
  973. elementDesc.reportOffsetInBits = parser.ExpectInt(jsonSpan);
  974. parser.AcceptSingleChar(jsonSpan, ',');
  975. parser.ExpectString(jsonSpan);
  976. parser.ExpectSingleChar(jsonSpan, ':');
  977. elementDesc.flags = (HIDElementFlags)parser.ExpectInt(jsonSpan);
  978. parser.ExpectSingleChar(jsonSpan, '}');
  979. elements.Add(elementDesc);
  980. }
  981. descriptor.elements = elements.ToArray();
  982. return descriptor;
  983. }
  984. catch (Exception)
  985. {
  986. Debug.LogWarning($"Couldn't parse HID descriptor with fast parser. Using fallback");
  987. return JsonUtility.FromJson<HIDDeviceDescriptor>(json);
  988. }
  989. #else
  990. return JsonUtility.FromJson<HIDDeviceDescriptor>(json);
  991. #endif
  992. }
  993. }
  994. /// <summary>
  995. /// Helper to quickly build descriptors for arbitrary HIDs.
  996. /// </summary>
  997. public struct HIDDeviceDescriptorBuilder
  998. {
  999. public UsagePage usagePage;
  1000. public int usage;
  1001. public HIDDeviceDescriptorBuilder(UsagePage usagePage, int usage)
  1002. : this()
  1003. {
  1004. this.usagePage = usagePage;
  1005. this.usage = usage;
  1006. }
  1007. public HIDDeviceDescriptorBuilder(GenericDesktop usage)
  1008. : this(UsagePage.GenericDesktop, (int)usage)
  1009. {
  1010. }
  1011. public HIDDeviceDescriptorBuilder StartReport(HIDReportType reportType, int reportId = 1)
  1012. {
  1013. m_CurrentReportId = reportId;
  1014. m_CurrentReportType = reportType;
  1015. m_CurrentReportOffsetInBits = 8; // Report ID.
  1016. return this;
  1017. }
  1018. public HIDDeviceDescriptorBuilder AddElement(UsagePage usagePage, int usage, int sizeInBits)
  1019. {
  1020. if (m_Elements == null)
  1021. {
  1022. m_Elements = new List<HIDElementDescriptor>();
  1023. }
  1024. else
  1025. {
  1026. // Make sure the usage and usagePage combination is unique.
  1027. foreach (var element in m_Elements)
  1028. {
  1029. // Skip elements that aren't in the same report.
  1030. if (element.reportId != m_CurrentReportId || element.reportType != m_CurrentReportType)
  1031. continue;
  1032. if (element.usagePage == usagePage && element.usage == usage)
  1033. throw new InvalidOperationException(
  1034. $"Cannot add two elements with the same usage page '{usagePage}' and usage '0x{usage:X} the to same device");
  1035. }
  1036. }
  1037. m_Elements.Add(new HIDElementDescriptor
  1038. {
  1039. usage = usage,
  1040. usagePage = usagePage,
  1041. reportOffsetInBits = m_CurrentReportOffsetInBits,
  1042. reportSizeInBits = sizeInBits,
  1043. reportType = m_CurrentReportType,
  1044. reportId = m_CurrentReportId
  1045. });
  1046. m_CurrentReportOffsetInBits += sizeInBits;
  1047. return this;
  1048. }
  1049. public HIDDeviceDescriptorBuilder AddElement(GenericDesktop usage, int sizeInBits)
  1050. {
  1051. return AddElement(UsagePage.GenericDesktop, (int)usage, sizeInBits);
  1052. }
  1053. public HIDDeviceDescriptorBuilder WithPhysicalMinMax(int min, int max)
  1054. {
  1055. var index = m_Elements.Count - 1;
  1056. if (index < 0)
  1057. throw new InvalidOperationException("No element has been added to the descriptor yet");
  1058. var element = m_Elements[index];
  1059. element.physicalMin = min;
  1060. element.physicalMax = max;
  1061. m_Elements[index] = element;
  1062. return this;
  1063. }
  1064. public HIDDeviceDescriptorBuilder WithLogicalMinMax(int min, int max)
  1065. {
  1066. var index = m_Elements.Count - 1;
  1067. if (index < 0)
  1068. throw new InvalidOperationException("No element has been added to the descriptor yet");
  1069. var element = m_Elements[index];
  1070. element.logicalMin = min;
  1071. element.logicalMax = max;
  1072. m_Elements[index] = element;
  1073. return this;
  1074. }
  1075. public HIDDeviceDescriptor Finish()
  1076. {
  1077. var descriptor = new HIDDeviceDescriptor
  1078. {
  1079. usage = usage,
  1080. usagePage = usagePage,
  1081. elements = m_Elements?.ToArray(),
  1082. collections = m_Collections?.ToArray(),
  1083. };
  1084. return descriptor;
  1085. }
  1086. private int m_CurrentReportId;
  1087. private HIDReportType m_CurrentReportType;
  1088. private int m_CurrentReportOffsetInBits;
  1089. private List<HIDElementDescriptor> m_Elements;
  1090. private List<HIDCollectionDescriptor> m_Collections;
  1091. private int m_InputReportSize;
  1092. private int m_OutputReportSize;
  1093. private int m_FeatureReportSize;
  1094. }
  1095. /// <summary>
  1096. /// Enumeration of HID usage pages.
  1097. /// </summary>00
  1098. /// <remarks>
  1099. /// Note that some of the values are actually ranges.
  1100. /// </remarks>
  1101. /// <seealso href="http://www.usb.org/developers/hidpage/Hut1_12v2.pdf"/>
  1102. public enum UsagePage
  1103. {
  1104. Undefined = 0x00,
  1105. GenericDesktop = 0x01,
  1106. Simulation = 0x02,
  1107. VRControls = 0x03,
  1108. SportControls = 0x04,
  1109. GameControls = 0x05,
  1110. GenericDeviceControls = 0x06,
  1111. Keyboard = 0x07,
  1112. LEDs = 0x08,
  1113. Button = 0x09,
  1114. Ordinal = 0x0A,
  1115. Telephony = 0x0B,
  1116. Consumer = 0x0C,
  1117. Digitizer = 0x0D,
  1118. PID = 0x0F,
  1119. Unicode = 0x10,
  1120. AlphanumericDisplay = 0x14,
  1121. MedicalInstruments = 0x40,
  1122. Monitor = 0x80, // Starts here and goes up to 0x83.
  1123. Power = 0x84, // Starts here and goes up to 0x87.
  1124. BarCodeScanner = 0x8C,
  1125. MagneticStripeReader = 0x8E,
  1126. Camera = 0x90,
  1127. Arcade = 0x91,
  1128. VendorDefined = 0xFF00, // Starts here and goes up to 0xFFFF.
  1129. }
  1130. /// <summary>
  1131. /// Usages in the GenericDesktop HID usage page.
  1132. /// </summary>
  1133. /// <seealso href="http://www.usb.org/developers/hidpage/Hut1_12v2.pdf"/>
  1134. public enum GenericDesktop
  1135. {
  1136. Undefined = 0x00,
  1137. Pointer = 0x01,
  1138. Mouse = 0x02,
  1139. Joystick = 0x04,
  1140. Gamepad = 0x05,
  1141. Keyboard = 0x06,
  1142. Keypad = 0x07,
  1143. MultiAxisController = 0x08,
  1144. TabletPCControls = 0x09,
  1145. AssistiveControl = 0x0A,
  1146. X = 0x30,
  1147. Y = 0x31,
  1148. Z = 0x32,
  1149. Rx = 0x33,
  1150. Ry = 0x34,
  1151. Rz = 0x35,
  1152. Slider = 0x36,
  1153. Dial = 0x37,
  1154. Wheel = 0x38,
  1155. HatSwitch = 0x39,
  1156. CountedBuffer = 0x3A,
  1157. ByteCount = 0x3B,
  1158. MotionWakeup = 0x3C,
  1159. Start = 0x3D,
  1160. Select = 0x3E,
  1161. Vx = 0x40,
  1162. Vy = 0x41,
  1163. Vz = 0x42,
  1164. Vbrx = 0x43,
  1165. Vbry = 0x44,
  1166. Vbrz = 0x45,
  1167. Vno = 0x46,
  1168. FeatureNotification = 0x47,
  1169. ResolutionMultiplier = 0x48,
  1170. SystemControl = 0x80,
  1171. SystemPowerDown = 0x81,
  1172. SystemSleep = 0x82,
  1173. SystemWakeUp = 0x83,
  1174. SystemContextMenu = 0x84,
  1175. SystemMainMenu = 0x85,
  1176. SystemAppMenu = 0x86,
  1177. SystemMenuHelp = 0x87,
  1178. SystemMenuExit = 0x88,
  1179. SystemMenuSelect = 0x89,
  1180. SystemMenuRight = 0x8A,
  1181. SystemMenuLeft = 0x8B,
  1182. SystemMenuUp = 0x8C,
  1183. SystemMenuDown = 0x8D,
  1184. SystemColdRestart = 0x8E,
  1185. SystemWarmRestart = 0x8F,
  1186. DpadUp = 0x90,
  1187. DpadDown = 0x91,
  1188. DpadRight = 0x92,
  1189. DpadLeft = 0x93,
  1190. SystemDock = 0xA0,
  1191. SystemUndock = 0xA1,
  1192. SystemSetup = 0xA2,
  1193. SystemBreak = 0xA3,
  1194. SystemDebuggerBreak = 0xA4,
  1195. ApplicationBreak = 0xA5,
  1196. ApplicationDebuggerBreak = 0xA6,
  1197. SystemSpeakerMute = 0xA7,
  1198. SystemHibernate = 0xA8,
  1199. SystemDisplayInvert = 0xB0,
  1200. SystemDisplayInternal = 0xB1,
  1201. SystemDisplayExternal = 0xB2,
  1202. SystemDisplayBoth = 0xB3,
  1203. SystemDisplayDual = 0xB4,
  1204. SystemDisplayToggleIntExt = 0xB5,
  1205. SystemDisplaySwapPrimarySecondary = 0xB6,
  1206. SystemDisplayLCDAutoScale = 0xB7
  1207. }
  1208. public enum Simulation
  1209. {
  1210. Undefined = 0x00,
  1211. FlightSimulationDevice = 0x01,
  1212. AutomobileSimulationDevice = 0x02,
  1213. TankSimulationDevice = 0x03,
  1214. SpaceshipSimulationDevice = 0x04,
  1215. SubmarineSimulationDevice = 0x05,
  1216. SailingSimulationDevice = 0x06,
  1217. MotorcycleSimulationDevice = 0x07,
  1218. SportsSimulationDevice = 0x08,
  1219. AirplaneSimulationDevice = 0x09,
  1220. HelicopterSimulationDevice = 0x0A,
  1221. MagicCarpetSimulationDevice = 0x0B,
  1222. BicylcleSimulationDevice = 0x0C,
  1223. FlightControlStick = 0x20,
  1224. FlightStick = 0x21,
  1225. CyclicControl = 0x22,
  1226. CyclicTrim = 0x23,
  1227. FlightYoke = 0x24,
  1228. TrackControl = 0x25,
  1229. Aileron = 0xB0,
  1230. AileronTrim = 0xB1,
  1231. AntiTorqueControl = 0xB2,
  1232. AutopilotEnable = 0xB3,
  1233. ChaffRelease = 0xB4,
  1234. CollectiveControl = 0xB5,
  1235. DiveBreak = 0xB6,
  1236. ElectronicCountermeasures = 0xB7,
  1237. Elevator = 0xB8,
  1238. ElevatorTrim = 0xB9,
  1239. Rudder = 0xBA,
  1240. Throttle = 0xBB,
  1241. FlightCommunications = 0xBC,
  1242. FlareRelease = 0xBD,
  1243. LandingGear = 0xBE,
  1244. ToeBreak = 0xBF,
  1245. Trigger = 0xC0,
  1246. WeaponsArm = 0xC1,
  1247. WeaponsSelect = 0xC2,
  1248. WingFlaps = 0xC3,
  1249. Accelerator = 0xC4,
  1250. Brake = 0xC5,
  1251. Clutch = 0xC6,
  1252. Shifter = 0xC7,
  1253. Steering = 0xC8,
  1254. TurretDirection = 0xC9,
  1255. BarrelElevation = 0xCA,
  1256. DivePlane = 0xCB,
  1257. Ballast = 0xCC,
  1258. BicycleCrank = 0xCD,
  1259. HandleBars = 0xCE,
  1260. FrontBrake = 0xCF,
  1261. RearBrake = 0xD0
  1262. }
  1263. public enum Button
  1264. {
  1265. Undefined = 0,
  1266. Primary,
  1267. Secondary,
  1268. Tertiary
  1269. }
  1270. }
  1271. }