InputDeviceDescription.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. using System;
  2. using UnityEngine.InputSystem.Utilities;
  3. ////TODO: add a 'devicePath' property that platforms can use to relay their internal device locators
  4. //// (but do *not* take it into account when comparing descriptions for disconnected devices)
  5. namespace UnityEngine.InputSystem.Layouts
  6. {
  7. /// <summary>
  8. /// Metadata for an input device.
  9. /// </summary>
  10. /// <remarks>
  11. /// Device descriptions are mainly used to determine which <see cref="InputControlLayout"/>
  12. /// to create an actual <see cref="InputDevice"/> instance from. Each description is comprised
  13. /// of a set of properties that each are individually optional. However, for a description
  14. /// to be usable, at least some need to be set. Generally, the minimum viable description
  15. /// for a device is one with <see cref="deviceClass"/> filled out.
  16. ///
  17. /// <example>
  18. /// <code>
  19. /// // Device description equivalent to a generic gamepad with no
  20. /// // further information about the device.
  21. /// new InputDeviceDescription
  22. /// {
  23. /// deviceClass = "Gamepad"
  24. /// };
  25. /// </code>
  26. /// </example>
  27. ///
  28. /// Device descriptions will usually be supplied by the Unity runtime but can also be manually
  29. /// fed into the system using <see cref="InputSystem.AddDevice(InputDeviceDescription)"/>. The
  30. /// system will remember each device description it has seen regardless of whether it was
  31. /// able to successfully create a device from the description. To query the list of descriptions
  32. /// that for whatever reason did not result in a device being created, call <see
  33. /// cref="InputSystem.GetUnsupportedDevices()"/>.
  34. ///
  35. /// Whenever layout registrations in the system are changed (e.g. by calling <see
  36. /// cref="InputSystem.RegisterLayout{T}"/> or whenever <see cref="InputSettings.supportedDevices"/>
  37. /// is changed, the system will go through the list of unsupported devices itself and figure out
  38. /// if there are device descriptions that now it can turn into devices. The same also applies
  39. /// in reverse; if, for example, a layout is removed that is currently used a device, the
  40. /// device will be removed and its description (if any) will be placed on the list of
  41. /// unsupported devices.
  42. /// </remarks>
  43. /// <seealso cref="InputDevice.description"/>
  44. /// <seealso cref="InputDeviceMatcher"/>
  45. [Serializable]
  46. public struct InputDeviceDescription : IEquatable<InputDeviceDescription>
  47. {
  48. /// <summary>
  49. /// How we talk to the device; usually name of the underlying backend that feeds
  50. /// state for the device (e.g. "HID" or "XInput").
  51. /// </summary>
  52. /// <value>Name of interface through which the device is reported.</value>
  53. /// <see cref="InputDeviceMatcher.WithInterface"/>
  54. public string interfaceName
  55. {
  56. get => m_InterfaceName;
  57. set => m_InterfaceName = value;
  58. }
  59. /// <summary>
  60. /// What the interface thinks the device classifies as.
  61. /// </summary>
  62. /// <value>Broad classification of device.</value>
  63. /// <remarks>
  64. /// If there is no layout specifically matching a device description,
  65. /// the device class is used as as fallback. If, for example, this field
  66. /// is set to "Gamepad", the "Gamepad" layout is used as a fallback.
  67. /// </remarks>
  68. /// <seealso cref="InputDeviceMatcher.WithDeviceClass"/>
  69. public string deviceClass
  70. {
  71. get => m_DeviceClass;
  72. set => m_DeviceClass = value;
  73. }
  74. /// <summary>
  75. /// Name of the vendor that produced the device.
  76. /// </summary>
  77. /// <value>Name of manufacturer.</value>
  78. /// <seealso cref="InputDeviceMatcher.WithManufacturer"/>
  79. public string manufacturer
  80. {
  81. get => m_Manufacturer;
  82. set => m_Manufacturer = value;
  83. }
  84. /// <summary>
  85. /// Name of the product assigned by the vendor to the device.
  86. /// </summary>
  87. /// <value>Name of product.</value>
  88. /// <seealso cref="InputDeviceMatcher.WithProduct"/>
  89. public string product
  90. {
  91. get => m_Product;
  92. set => m_Product = value;
  93. }
  94. /// <summary>
  95. /// If available, serial number for the device.
  96. /// </summary>
  97. /// <value>Serial number of device.</value>
  98. public string serial
  99. {
  100. get => m_Serial;
  101. set => m_Serial = value;
  102. }
  103. /// <summary>
  104. /// Version string of the device and/or driver.
  105. /// </summary>
  106. /// <value>Version of device and/or driver.</value>
  107. /// <seealso cref="InputDeviceMatcher.WithVersion"/>
  108. public string version
  109. {
  110. get => m_Version;
  111. set => m_Version = value;
  112. }
  113. /// <summary>
  114. /// An optional JSON string listing device-specific capabilities.
  115. /// </summary>
  116. /// <value>Interface-specific listing of device capabilities.</value>
  117. /// <remarks>
  118. /// The primary use of this field is to allow custom layout factories
  119. /// to create layouts on the fly from in-depth device descriptions delivered
  120. /// by external APIs.
  121. ///
  122. /// In the case of HID, for example, this field contains a JSON representation
  123. /// of the HID descriptor (see <see cref="HID.HID.HIDDeviceDescriptor"/>) as
  124. /// reported by the device driver. This descriptor contains information about
  125. /// all I/O elements on the device which can be used to determine the control
  126. /// setup and data format used by the device.
  127. /// </remarks>
  128. /// <seealso cref="InputDeviceMatcher.WithCapability{T}"/>
  129. public string capabilities
  130. {
  131. get => m_Capabilities;
  132. set => m_Capabilities = value;
  133. }
  134. /// <summary>
  135. /// Whether any of the properties in the description are set.
  136. /// </summary>
  137. /// <value>True if any of <see cref="interfaceName"/>, <see cref="deviceClass"/>,
  138. /// <see cref="manufacturer"/>, <see cref="product"/>, <see cref="serial"/>,
  139. /// <see cref="version"/>, or <see cref="capabilities"/> is not <c>null</c> and
  140. /// not empty..</value>
  141. public bool empty =>
  142. string.IsNullOrEmpty(m_InterfaceName) &&
  143. string.IsNullOrEmpty(m_DeviceClass) &&
  144. string.IsNullOrEmpty(m_Manufacturer) &&
  145. string.IsNullOrEmpty(m_Product) &&
  146. string.IsNullOrEmpty(m_Serial) &&
  147. string.IsNullOrEmpty(m_Version) &&
  148. string.IsNullOrEmpty(m_Capabilities);
  149. /// <summary>
  150. /// Return a string representation of the description useful for
  151. /// debugging.
  152. /// </summary>
  153. /// <returns>A script representation of the description.</returns>
  154. public override string ToString()
  155. {
  156. var haveProduct = !string.IsNullOrEmpty(product);
  157. var haveManufacturer = !string.IsNullOrEmpty(manufacturer);
  158. var haveInterface = !string.IsNullOrEmpty(interfaceName);
  159. if (haveProduct && haveManufacturer)
  160. {
  161. if (haveInterface)
  162. return $"{manufacturer} {product} ({interfaceName})";
  163. return $"{manufacturer} {product}";
  164. }
  165. if (haveProduct)
  166. {
  167. if (haveInterface)
  168. return $"{product} ({interfaceName})";
  169. return product;
  170. }
  171. if (!string.IsNullOrEmpty(deviceClass))
  172. {
  173. if (haveInterface)
  174. return $"{deviceClass} ({interfaceName})";
  175. return deviceClass;
  176. }
  177. // For some HIDs on Windows, we don't get a product and manufacturer string even though
  178. // the HID is guaranteed to have a product and vendor ID. Resort to printing capabilities
  179. // which for HIDs at least include the product and vendor ID.
  180. if (!string.IsNullOrEmpty(capabilities))
  181. {
  182. const int kMaxCapabilitiesLength = 40;
  183. var caps = capabilities;
  184. if (capabilities.Length > kMaxCapabilitiesLength)
  185. caps = caps.Substring(0, kMaxCapabilitiesLength) + "...";
  186. if (haveInterface)
  187. return $"{caps} ({interfaceName})";
  188. return caps;
  189. }
  190. if (haveInterface)
  191. return interfaceName;
  192. return "<Empty Device Description>";
  193. }
  194. /// <summary>
  195. /// Compare the description to the given <paramref name="other"/> description.
  196. /// </summary>
  197. /// <param name="other">Another device description.</param>
  198. /// <returns>True if the two descriptions are equivalent.</returns>
  199. /// <remarks>
  200. /// Two descriptions are equivalent if all their properties are equal
  201. /// (ignore case).
  202. /// </remarks>
  203. public bool Equals(InputDeviceDescription other)
  204. {
  205. return m_InterfaceName.InvariantEqualsIgnoreCase(other.m_InterfaceName) &&
  206. m_DeviceClass.InvariantEqualsIgnoreCase(other.m_DeviceClass) &&
  207. m_Manufacturer.InvariantEqualsIgnoreCase(other.m_Manufacturer) &&
  208. m_Product.InvariantEqualsIgnoreCase(other.m_Product) &&
  209. m_Serial.InvariantEqualsIgnoreCase(other.m_Serial) &&
  210. m_Version.InvariantEqualsIgnoreCase(other.m_Version) &&
  211. ////REVIEW: this would ideally compare JSON contents not just the raw string
  212. m_Capabilities.InvariantEqualsIgnoreCase(other.m_Capabilities);
  213. }
  214. /// <summary>
  215. /// Compare the description to the given object.
  216. /// </summary>
  217. /// <param name="obj">An object.</param>
  218. /// <returns>True if <paramref name="obj"/> is an InputDeviceDescription
  219. /// equivalent to this one.</returns>
  220. /// <seealso cref="Equals(InputDeviceDescription)"/>
  221. public override bool Equals(object obj)
  222. {
  223. if (ReferenceEquals(null, obj))
  224. return false;
  225. return obj is InputDeviceDescription description && Equals(description);
  226. }
  227. /// <summary>
  228. /// Compute a hash code for the device description.
  229. /// </summary>
  230. /// <returns>A hash code.</returns>
  231. public override int GetHashCode()
  232. {
  233. unchecked
  234. {
  235. var hashCode = m_InterfaceName != null ? m_InterfaceName.GetHashCode() : 0;
  236. hashCode = (hashCode * 397) ^ (m_DeviceClass != null ? m_DeviceClass.GetHashCode() : 0);
  237. hashCode = (hashCode * 397) ^ (m_Manufacturer != null ? m_Manufacturer.GetHashCode() : 0);
  238. hashCode = (hashCode * 397) ^ (m_Product != null ? m_Product.GetHashCode() : 0);
  239. hashCode = (hashCode * 397) ^ (m_Serial != null ? m_Serial.GetHashCode() : 0);
  240. hashCode = (hashCode * 397) ^ (m_Version != null ? m_Version.GetHashCode() : 0);
  241. hashCode = (hashCode * 397) ^ (m_Capabilities != null ? m_Capabilities.GetHashCode() : 0);
  242. return hashCode;
  243. }
  244. }
  245. /// <summary>
  246. /// Compare the two device descriptions.
  247. /// </summary>
  248. /// <param name="left">First device description.</param>
  249. /// <param name="right">Second device description.</param>
  250. /// <returns>True if the two descriptions are equivalent.</returns>
  251. /// <seealso cref="Equals(InputDeviceDescription)"/>
  252. public static bool operator==(InputDeviceDescription left, InputDeviceDescription right)
  253. {
  254. return left.Equals(right);
  255. }
  256. /// <summary>
  257. /// Compare the two device descriptions for inequality.
  258. /// </summary>
  259. /// <param name="left">First device description.</param>
  260. /// <param name="right">Second device description.</param>
  261. /// <returns>True if the two descriptions are not equivalent.</returns>
  262. /// <seealso cref="Equals(InputDeviceDescription)"/>
  263. public static bool operator!=(InputDeviceDescription left, InputDeviceDescription right)
  264. {
  265. return !left.Equals(right);
  266. }
  267. /// <summary>
  268. /// Return a JSON representation of the device description.
  269. /// </summary>
  270. /// <returns>A JSON representation of the description.</returns>
  271. /// <remarks>
  272. /// <example>
  273. /// The result can be converted back into an InputDeviceDescription
  274. /// using <see cref="FromJson"/>.
  275. ///
  276. /// <code>
  277. /// var description = new InputDeviceDescription
  278. /// {
  279. /// interfaceName = "HID",
  280. /// product = "SomeDevice",
  281. /// capabilities = @"
  282. /// {
  283. /// ""vendorId"" : 0xABA,
  284. /// ""productId"" : 0xEFE
  285. /// }
  286. /// "
  287. /// };
  288. ///
  289. /// Debug.Log(description.ToJson());
  290. /// // Prints
  291. /// // {
  292. /// // "interface" : "HID",
  293. /// // "product" : "SomeDevice",
  294. /// // "capabilities" : "{ \"vendorId\" : 0xABA, \"productId\" : 0xEFF }"
  295. /// // }
  296. /// </code>
  297. /// </example>
  298. /// </remarks>
  299. /// <seealso cref="FromJson"/>
  300. public string ToJson()
  301. {
  302. var data = new DeviceDescriptionJson
  303. {
  304. @interface = interfaceName,
  305. type = deviceClass,
  306. product = product,
  307. manufacturer = manufacturer,
  308. serial = serial,
  309. version = version,
  310. capabilities = capabilities
  311. };
  312. return JsonUtility.ToJson(data, true);
  313. }
  314. /// <summary>
  315. /// Read an InputDeviceDescription from its JSON representation.
  316. /// </summary>
  317. /// <param name="json">String in JSON format.</param>
  318. /// <exception cref="ArgumentNullException"><paramref name="json"/> is <c>null</c>.</exception>
  319. /// <returns>The converted </returns>
  320. /// <exception cref="ArgumentException">There as a parse error in <paramref name="json"/>.
  321. /// </exception>
  322. /// <remarks>
  323. /// <example>
  324. /// <code>
  325. /// InputDeviceDescription.FromJson(@"
  326. /// {
  327. /// ""interface"" : ""HID"",
  328. /// ""product"" : ""SomeDevice""
  329. /// }
  330. /// ");
  331. /// </code>
  332. /// </example>
  333. /// </remarks>
  334. /// <seealso cref="ToJson"/>
  335. public static InputDeviceDescription FromJson(string json)
  336. {
  337. if (json == null)
  338. throw new ArgumentNullException(nameof(json));
  339. var data = JsonUtility.FromJson<DeviceDescriptionJson>(json);
  340. return new InputDeviceDescription
  341. {
  342. interfaceName = data.@interface,
  343. deviceClass = data.type,
  344. product = data.product,
  345. manufacturer = data.manufacturer,
  346. serial = data.serial,
  347. version = data.version,
  348. capabilities = data.capabilities
  349. };
  350. }
  351. internal static bool ComparePropertyToDeviceDescriptor(string propertyName, string propertyValue, string deviceDescriptor)
  352. {
  353. // We use JsonParser instead of JsonUtility.Parse in order to not allocate GC memory here.
  354. var json = new JsonParser(deviceDescriptor);
  355. if (!json.NavigateToProperty(propertyName))
  356. {
  357. if (string.IsNullOrEmpty(propertyValue))
  358. return true;
  359. return false;
  360. }
  361. return json.CurrentPropertyHasValueEqualTo(propertyValue);
  362. }
  363. [SerializeField] private string m_InterfaceName;
  364. [SerializeField] private string m_DeviceClass;
  365. [SerializeField] private string m_Manufacturer;
  366. [SerializeField] private string m_Product;
  367. [SerializeField] private string m_Serial;
  368. [SerializeField] private string m_Version;
  369. [SerializeField] private string m_Capabilities;
  370. private struct DeviceDescriptionJson
  371. {
  372. public string @interface;
  373. public string type;
  374. public string product;
  375. public string serial;
  376. public string version;
  377. public string manufacturer;
  378. public string capabilities;
  379. }
  380. }
  381. }