InputDeviceMatcher.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text.RegularExpressions;
  5. using UnityEngine.InputSystem.Utilities;
  6. namespace UnityEngine.InputSystem.Layouts
  7. {
  8. /// <summary>
  9. /// Specification that can be matched against an <see cref="InputDeviceDescription"/>. This is
  10. /// used to find which <see cref="InputControlLayout"/> to create for a device when it is discovered.
  11. /// </summary>
  12. /// <remarks>
  13. /// Each matcher is basically a set of key/value pairs where each value may either be
  14. /// a regular expression or a plain value object. The central method for testing a given matcher
  15. /// against an <see cref="InputDeviceDescription"/> is <see cref="MatchPercentage"/>.
  16. ///
  17. /// Various helper methods such as <see cref="WithInterface"/> or <see cref="WithCapability{TValue}"/>
  18. /// assist with creating matchers.
  19. ///
  20. /// <example>
  21. /// <code>
  22. /// // A matcher that matches a PS4 controller by name.
  23. /// new InputDeviceMatcher()
  24. /// .WithInterface("HID")
  25. /// .WithManufacturer("Sony.+Entertainment") // Regular expression
  26. /// .WithProduct("Wireless Controller"));
  27. ///
  28. /// // A matcher that matches the same controller by PID and VID.
  29. /// new InputDeviceMatcher()
  30. /// .WithInterface("HID")
  31. /// .WithCapability("vendorId", 0x54C) // Sony Entertainment.
  32. /// .WithCapability("productId", 0x9CC)); // Wireless controller.
  33. /// </code>
  34. /// </example>
  35. ///
  36. /// For each registered <see cref="InputControlLayout"/> in the system that represents
  37. /// a device, arbitrary many matchers can be added. A matcher can be supplied either
  38. /// at registration time or at any point after using <see cref="InputSystem.RegisterLayoutMatcher"/>.
  39. ///
  40. /// <example>
  41. /// <code>
  42. /// // Supply a matcher at registration time.
  43. /// InputSystem.RegisterLayout&lt;DualShock4GamepadHID&gt;(
  44. /// matches: new InputDeviceMatcher()
  45. /// .WithInterface("HID")
  46. /// .WithCapability("vendorId", 0x54C) // Sony Entertainment.
  47. /// .WithCapability("productId", 0x9CC)); // Wireless controller.
  48. ///
  49. /// // Supply a matcher for an already registered layout.
  50. /// // This can be called repeatedly and will add another matcher
  51. /// // each time.
  52. /// InputSystem.RegisterLayoutMatcher&lt;DualShock4GamepadHID&gt;(
  53. /// matches: new InputDeviceMatcher()
  54. /// .WithInterface("HID")
  55. /// .WithManufacturer("Sony.+Entertainment")
  56. /// .WithProduct("Wireless Controller"));
  57. /// </code>
  58. /// </example>
  59. /// </remarks>
  60. /// <seealso cref="InputDeviceDescription"/>
  61. /// <seealso cref="InputDevice.description"/>
  62. /// <seealso cref="InputSystem.RegisterLayoutMatcher"/>
  63. public struct InputDeviceMatcher : IEquatable<InputDeviceMatcher>
  64. {
  65. private KeyValuePair<InternedString, object>[] m_Patterns;
  66. /// <summary>
  67. /// If true, the matcher has been default-initialized and contains no
  68. /// matching <see cref="patterns"/>.
  69. /// </summary>
  70. /// <value>Whether the matcher contains any matching patterns.</value>
  71. /// <seealso cref="patterns"/>
  72. public bool empty => m_Patterns == null;
  73. /// <summary>
  74. /// The list of patterns to match.
  75. /// </summary>
  76. /// <value>List of matching patterns.</value>
  77. /// <remarks>
  78. /// Each pattern is comprised of a key and a value. The key determines which part
  79. /// of an <see cref="InputDeviceDescription"/> to match.
  80. ///
  81. /// The value represents the expected value. This can be either a plain string
  82. /// (matched case-insensitive) or a regular expression.
  83. /// </remarks>
  84. /// <seealso cref="WithInterface"/>
  85. /// <seealso cref="WithCapability{TValue}"/>
  86. /// <seealso cref="WithProduct"/>
  87. /// <seealso cref="WithManufacturer"/>
  88. /// <seealso cref="WithVersion"/>
  89. /// <seealso cref="WithDeviceClass"/>
  90. public IEnumerable<KeyValuePair<string, object>> patterns
  91. {
  92. get
  93. {
  94. if (m_Patterns == null)
  95. yield break;
  96. var count = m_Patterns.Length;
  97. for (var i = 0; i < count; ++i)
  98. yield return new KeyValuePair<string, object>(m_Patterns[i].Key.ToString(), m_Patterns[i].Value);
  99. }
  100. }
  101. /// <summary>
  102. /// Add a pattern to <see cref="patterns"/> to match an <see cref="InputDeviceDescription.interfaceName"/>.
  103. /// </summary>
  104. /// <param name="pattern">String to match.</param>
  105. /// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
  106. /// a regular expression.</param>
  107. /// <returns>The modified device matcher with the added pattern.</returns>
  108. /// <seealso cref="InputDeviceDescription.interfaceName"/>
  109. public InputDeviceMatcher WithInterface(string pattern, bool supportRegex = true)
  110. {
  111. return With(kInterfaceKey, pattern, supportRegex);
  112. }
  113. /// <summary>
  114. /// Add a pattern to <see cref="patterns"/> to match a <see cref="InputDeviceDescription.deviceClass"/>.
  115. /// </summary>
  116. /// <param name="pattern">String to match.</param>
  117. /// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
  118. /// a regular expression.</param>
  119. /// <returns>The modified device matcher with the added pattern.</returns>
  120. /// <seealso cref="InputDeviceDescription.deviceClass"/>
  121. public InputDeviceMatcher WithDeviceClass(string pattern, bool supportRegex = true)
  122. {
  123. return With(kDeviceClassKey, pattern, supportRegex);
  124. }
  125. /// <summary>
  126. /// Add a pattern to <see cref="patterns"/> to match a <see cref="InputDeviceDescription.manufacturer"/>.
  127. /// </summary>
  128. /// <param name="pattern">String to match.</param>
  129. /// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
  130. /// a regular expression.</param>
  131. /// <returns>The modified device matcher with the added pattern.</returns>
  132. /// <seealso cref="InputDeviceDescription.manufacturer"/>
  133. public InputDeviceMatcher WithManufacturer(string pattern, bool supportRegex = true)
  134. {
  135. return With(kManufacturerKey, pattern, supportRegex);
  136. }
  137. /// <summary>
  138. /// Add a pattern to <see cref="patterns"/> to match a <see cref="InputDeviceDescription.product"/>.
  139. /// </summary>
  140. /// <param name="pattern">String to match.</param>
  141. /// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
  142. /// a regular expression.</param>
  143. /// <returns>The modified device matcher with the added pattern.</returns>
  144. /// <seealso cref="InputDeviceDescription.product"/>
  145. public InputDeviceMatcher WithProduct(string pattern, bool supportRegex = true)
  146. {
  147. return With(kProductKey, pattern, supportRegex);
  148. }
  149. /// <summary>
  150. /// Add a pattern to <see cref="patterns"/> to match a <see cref="InputDeviceDescription.version"/>.
  151. /// </summary>
  152. /// <param name="pattern">String to match.</param>
  153. /// <param name="supportRegex">If true (default), <paramref name="pattern"/> can be
  154. /// a regular expression.</param>
  155. /// <returns>The modified device matcher with the added pattern.</returns>
  156. /// <seealso cref="InputDeviceDescription.version"/>
  157. public InputDeviceMatcher WithVersion(string pattern, bool supportRegex = true)
  158. {
  159. return With(kVersionKey, pattern, supportRegex);
  160. }
  161. /// <summary>
  162. /// Add a pattern to <see cref="patterns"/> to match an individual capability in <see cref="InputDeviceDescription.capabilities"/>.
  163. /// </summary>
  164. /// <param name="path">Path to the JSON property using '/' as a separator,
  165. /// e.g. <c>"elements/count"</c>.</param>
  166. /// <param name="value">Value to match. This can be a string, a regular expression,
  167. /// a boolean, an integer, or a float. Floating-point numbers are matched with respect
  168. /// for <c>Mathf.Epsilon</c>. Values are converted between types automatically as
  169. /// needed (meaning that a bool can be compared to a string, for example).</param>
  170. /// <typeparam name="TValue">Type of value to match.</typeparam>
  171. /// <returns>The modified device matcher with the added pattern.</returns>
  172. /// <remarks>
  173. /// Capabilities are stored as JSON strings in <see cref="InputDeviceDescription.capabilities"/>.
  174. /// A matcher has the ability to match specific properties from the JSON object
  175. /// contained in the capabilities string.
  176. ///
  177. /// <example>
  178. /// <code>
  179. /// // The description for a HID will usually have a HIDDeviceDescriptor in
  180. /// // JSON format found on its InputDeviceDescription.capabilities. So, a
  181. /// // real-world device description could look the equivalent of this:
  182. /// var description = new InputDeviceDescription
  183. /// {
  184. /// interfaceName = "HID",
  185. /// capabilities = new HID.HIDDeviceDescriptor
  186. /// {
  187. /// vendorId = 0x54C,
  188. /// productId = 0x9CC
  189. /// }.ToJson()
  190. /// };
  191. ///
  192. /// // We can create a device matcher that looks for those to properties
  193. /// // directly in the JSON object.
  194. /// new InputDeviceMatcher()
  195. /// .WithCapability("vendorId", 0x54C)
  196. /// .WithCapability("productId", 0x9CC);
  197. /// </code>
  198. /// </example>
  199. ///
  200. /// Properties in nested objects can be referenced by separating properties
  201. /// with <c>/</c> and properties in arrays can be indexed with <c>[..]</c>.
  202. /// </remarks>
  203. /// <seealso cref="InputDeviceDescription.capabilities"/>
  204. public InputDeviceMatcher WithCapability<TValue>(string path, TValue value)
  205. {
  206. return With(new InternedString(path), value);
  207. }
  208. private InputDeviceMatcher With(InternedString key, object value, bool supportRegex = true)
  209. {
  210. // If it's a string, check whether it's a regex.
  211. if (supportRegex && value is string str)
  212. {
  213. var mayBeRegex = !str.All(ch => char.IsLetterOrDigit(ch) || char.IsWhiteSpace(ch)) &&
  214. !double.TryParse(str, out var _); // Avoid '.' in floats forcing the value to be a regex.
  215. if (mayBeRegex)
  216. value = new Regex(str, RegexOptions.IgnoreCase);
  217. }
  218. // Add to list.
  219. var result = this;
  220. ArrayHelpers.Append(ref result.m_Patterns, new KeyValuePair<InternedString, object>(key, value));
  221. return result;
  222. }
  223. /// <summary>
  224. /// Return the level of matching to the given <paramref name="deviceDescription"/>.
  225. /// </summary>
  226. /// <param name="deviceDescription">A device description.</param>
  227. /// <returns>A score usually in the range between 0 and 1.</returns>
  228. /// <remarks>
  229. /// The algorithm computes a score of how well the matcher matches the given description.
  230. /// Essentially, a matcher that matches every single property that is present (as in
  231. /// not <c>null</c> and not an empty string) in <paramref name="deviceDescription"/> receives
  232. /// a score of 1, a matcher that matches none a score of 0. Matches that match only a subset
  233. /// receive a score in-between.
  234. ///
  235. /// An exception to this are capabilities. Every single match of a capability is counted
  236. /// as one property match and added to the score. This means that matchers that match
  237. /// on multiple capabilities may actually achieve a score &gt;1.
  238. ///
  239. /// <example>
  240. /// <code>
  241. /// var description = new InputDeviceDescription
  242. /// {
  243. /// interfaceName = "HID",
  244. /// product = "MadeUpDevice",
  245. /// capabilities = new HID.HIDDeviceDescriptor
  246. /// {
  247. /// vendorId = 0xABC,
  248. /// productId = 0xDEF
  249. /// }.ToJson()
  250. /// };
  251. ///
  252. /// // This matcher will achieve a score of 0.666 (2/3) as it
  253. /// // matches two out of three available properties.
  254. /// new InputDeviceMatcher()
  255. /// .WithInterface("HID")
  256. /// .WithProduct("MadeUpDevice");
  257. ///
  258. /// // This matcher will achieve a score of 1 despite not matching
  259. /// // 'product'. The reason is that it matches two keys in
  260. /// // 'capabilities'.
  261. /// new InputDeviceMatcher()
  262. /// .WithInterface("HID")
  263. /// .WithCapability("vendorId", 0xABC)
  264. /// .WithCapability("productId", 0xDEF);
  265. /// </code>
  266. /// </example>
  267. /// </remarks>
  268. public float MatchPercentage(InputDeviceDescription deviceDescription)
  269. {
  270. if (empty)
  271. return 0;
  272. // Go through all patterns. Score is 0 if any of the patterns
  273. // doesn't match.
  274. var numPatterns = m_Patterns.Length;
  275. for (var i = 0; i < numPatterns; ++i)
  276. {
  277. var key = m_Patterns[i].Key;
  278. var pattern = m_Patterns[i].Value;
  279. if (key == kInterfaceKey)
  280. {
  281. if (string.IsNullOrEmpty(deviceDescription.interfaceName)
  282. || !MatchSingleProperty(pattern, deviceDescription.interfaceName))
  283. return 0;
  284. }
  285. else if (key == kDeviceClassKey)
  286. {
  287. if (string.IsNullOrEmpty(deviceDescription.deviceClass)
  288. || !MatchSingleProperty(pattern, deviceDescription.deviceClass))
  289. return 0;
  290. }
  291. else if (key == kManufacturerKey)
  292. {
  293. if (string.IsNullOrEmpty(deviceDescription.manufacturer)
  294. || !MatchSingleProperty(pattern, deviceDescription.manufacturer))
  295. return 0;
  296. }
  297. else if (key == kProductKey)
  298. {
  299. if (string.IsNullOrEmpty(deviceDescription.product)
  300. || !MatchSingleProperty(pattern, deviceDescription.product))
  301. return 0;
  302. }
  303. else if (key == kVersionKey)
  304. {
  305. if (string.IsNullOrEmpty(deviceDescription.version)
  306. || !MatchSingleProperty(pattern, deviceDescription.version))
  307. return 0;
  308. }
  309. else
  310. {
  311. // Capabilities match. Take the key as a path into the JSON
  312. // object and match the value found at the given path.
  313. if (string.IsNullOrEmpty(deviceDescription.capabilities))
  314. return 0;
  315. var graph = new JsonParser(deviceDescription.capabilities);
  316. if (!graph.NavigateToProperty(key.ToString()) ||
  317. !graph.CurrentPropertyHasValueEqualTo(new JsonParser.JsonValue { type = JsonParser.JsonValueType.Any, anyValue = pattern}))
  318. return 0;
  319. }
  320. }
  321. // All patterns matched. Our score is determined by the number of properties
  322. // we matched against.
  323. var propertyCountInDescription = GetNumPropertiesIn(deviceDescription);
  324. var scorePerProperty = 1.0f / propertyCountInDescription;
  325. return numPatterns * scorePerProperty;
  326. }
  327. private static bool MatchSingleProperty(object pattern, string value)
  328. {
  329. // String match.
  330. if (pattern is string str)
  331. return string.Compare(str, value, StringComparison.InvariantCultureIgnoreCase) == 0;
  332. // Regex match.
  333. if (pattern is Regex regex)
  334. return regex.IsMatch(value);
  335. return false;
  336. }
  337. private static int GetNumPropertiesIn(InputDeviceDescription description)
  338. {
  339. var count = 0;
  340. if (!string.IsNullOrEmpty(description.interfaceName))
  341. count += 1;
  342. if (!string.IsNullOrEmpty(description.deviceClass))
  343. count += 1;
  344. if (!string.IsNullOrEmpty(description.manufacturer))
  345. count += 1;
  346. if (!string.IsNullOrEmpty(description.product))
  347. count += 1;
  348. if (!string.IsNullOrEmpty(description.version))
  349. count += 1;
  350. if (!string.IsNullOrEmpty(description.capabilities))
  351. count += 1;
  352. return count;
  353. }
  354. /// <summary>
  355. /// Produce a matcher that matches the given device description verbatim.
  356. /// </summary>
  357. /// <param name="deviceDescription">A device description.</param>
  358. /// <returns>A matcher that matches <paramref name="deviceDescription"/> exactly.</returns>
  359. /// <remarks>
  360. /// This method can be used to produce a matcher for an existing device description,
  361. /// e.g. when writing a layout <see cref="InputControlLayout.Builder"/> that produces
  362. /// layouts for devices on the fly.
  363. /// </remarks>
  364. public static InputDeviceMatcher FromDeviceDescription(InputDeviceDescription deviceDescription)
  365. {
  366. var matcher = new InputDeviceMatcher();
  367. if (!string.IsNullOrEmpty(deviceDescription.interfaceName))
  368. matcher = matcher.WithInterface(deviceDescription.interfaceName, false);
  369. if (!string.IsNullOrEmpty(deviceDescription.deviceClass))
  370. matcher = matcher.WithDeviceClass(deviceDescription.deviceClass, false);
  371. if (!string.IsNullOrEmpty(deviceDescription.manufacturer))
  372. matcher = matcher.WithManufacturer(deviceDescription.manufacturer, false);
  373. if (!string.IsNullOrEmpty(deviceDescription.product))
  374. matcher = matcher.WithProduct(deviceDescription.product, false);
  375. if (!string.IsNullOrEmpty(deviceDescription.version))
  376. matcher = matcher.WithVersion(deviceDescription.version, false);
  377. // We don't include capabilities in this conversion.
  378. return matcher;
  379. }
  380. /// <summary>
  381. /// Return a string representation useful for debugging. Lists the
  382. /// <see cref="patterns"/> contained in the matcher.
  383. /// </summary>
  384. /// <returns>A string representation of the matcher.</returns>
  385. public override string ToString()
  386. {
  387. if (empty)
  388. return "<empty>";
  389. var result = string.Empty;
  390. foreach (var pattern in m_Patterns)
  391. {
  392. if (result.Length > 0)
  393. result += $",{pattern.Key}={pattern.Value}";
  394. else
  395. result += $"{pattern.Key}={pattern.Value}";
  396. }
  397. return result;
  398. }
  399. /// <summary>
  400. /// Test whether this matcher is equivalent to the <paramref name="other"/> matcher.
  401. /// </summary>
  402. /// <param name="other">Another device matcher.</param>
  403. /// <returns>True if the two matchers are equivalent.</returns>
  404. /// <remarks>
  405. /// Two matchers are equivalent if they contain the same number of patterns and the
  406. /// same pattern occurs in each of the matchers. Order of the patterns does not
  407. /// matter.
  408. /// </remarks>
  409. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "False positive.")]
  410. public bool Equals(InputDeviceMatcher other)
  411. {
  412. if (m_Patterns == other.m_Patterns)
  413. return true;
  414. if (m_Patterns == null || other.m_Patterns == null)
  415. return false;
  416. if (m_Patterns.Length != other.m_Patterns.Length)
  417. return false;
  418. // Pattern count matches. Compare pattern by pattern. Order of patterns doesn't matter.
  419. for (var i = 0; i < m_Patterns.Length; ++i)
  420. {
  421. var thisPattern = m_Patterns[i];
  422. var foundPattern = false;
  423. for (var n = 0; n < m_Patterns.Length; ++n)
  424. {
  425. var otherPattern = other.m_Patterns[n];
  426. if (thisPattern.Key != otherPattern.Key)
  427. continue;
  428. if (!thisPattern.Value.Equals(otherPattern.Value))
  429. return false;
  430. foundPattern = true;
  431. break;
  432. }
  433. if (!foundPattern)
  434. return false;
  435. }
  436. return true;
  437. }
  438. /// <summary>
  439. /// Compare this matcher to another.
  440. /// </summary>
  441. /// <param name="obj">A matcher object or <c>null</c>.</param>
  442. /// <returns>True if the matcher is equivalent.</returns>
  443. /// <seealso cref="Equals(InputDeviceMatcher)"/>
  444. public override bool Equals(object obj)
  445. {
  446. if (ReferenceEquals(null, obj))
  447. return false;
  448. return obj is InputDeviceMatcher matcher && Equals(matcher);
  449. }
  450. /// <summary>
  451. /// Compare two matchers for equivalence.
  452. /// </summary>
  453. /// <param name="left">First device matcher.</param>
  454. /// <param name="right">Second device matcher.</param>
  455. /// <returns>True if the two matchers are equivalent.</returns>
  456. /// <seealso cref="Equals(InputDeviceMatcher)"/>
  457. public static bool operator==(InputDeviceMatcher left, InputDeviceMatcher right)
  458. {
  459. return left.Equals(right);
  460. }
  461. /// <summary>
  462. /// Compare two matchers for non-equivalence.
  463. /// </summary>
  464. /// <param name="left">First device matcher.</param>
  465. /// <param name="right">Second device matcher.</param>
  466. /// <returns>True if the two matchers are not equivalent.</returns>
  467. /// <seealso cref="Equals(InputDeviceMatcher)"/>
  468. public static bool operator!=(InputDeviceMatcher left, InputDeviceMatcher right)
  469. {
  470. return !(left == right);
  471. }
  472. /// <summary>
  473. /// Compute a hash code for the device matcher.
  474. /// </summary>
  475. /// <returns>A hash code for the matcher.</returns>
  476. public override int GetHashCode()
  477. {
  478. return m_Patterns != null ? m_Patterns.GetHashCode() : 0;
  479. }
  480. private static readonly InternedString kInterfaceKey = new InternedString("interface");
  481. private static readonly InternedString kDeviceClassKey = new InternedString("deviceClass");
  482. private static readonly InternedString kManufacturerKey = new InternedString("manufacturer");
  483. private static readonly InternedString kProductKey = new InternedString("product");
  484. private static readonly InternedString kVersionKey = new InternedString("version");
  485. [Serializable]
  486. internal struct MatcherJson
  487. {
  488. public string @interface;
  489. public string[] interfaces;
  490. public string deviceClass;
  491. public string[] deviceClasses;
  492. public string manufacturer;
  493. public string[] manufacturers;
  494. public string product;
  495. public string[] products;
  496. public string version;
  497. public string[] versions;
  498. public Capability[] capabilities;
  499. public struct Capability
  500. {
  501. public string path;
  502. public string value;
  503. }
  504. public static MatcherJson FromMatcher(InputDeviceMatcher matcher)
  505. {
  506. if (matcher.empty)
  507. return new MatcherJson();
  508. var json = new MatcherJson();
  509. foreach (var pattern in matcher.m_Patterns)
  510. {
  511. var key = pattern.Key;
  512. var value = pattern.Value.ToString();
  513. if (key == kInterfaceKey)
  514. {
  515. if (json.@interface == null)
  516. json.@interface = value;
  517. else
  518. ArrayHelpers.Append(ref json.interfaces, value);
  519. }
  520. else if (key == kDeviceClassKey)
  521. {
  522. if (json.deviceClass == null)
  523. json.deviceClass = value;
  524. else
  525. ArrayHelpers.Append(ref json.deviceClasses, value);
  526. }
  527. else if (key == kManufacturerKey)
  528. {
  529. if (json.manufacturer == null)
  530. json.manufacturer = value;
  531. else
  532. ArrayHelpers.Append(ref json.manufacturers, value);
  533. }
  534. else if (key == kProductKey)
  535. {
  536. if (json.product == null)
  537. json.product = value;
  538. else
  539. ArrayHelpers.Append(ref json.products, value);
  540. }
  541. else if (key == kVersionKey)
  542. {
  543. if (json.version == null)
  544. json.version = value;
  545. else
  546. ArrayHelpers.Append(ref json.versions, value);
  547. }
  548. else
  549. {
  550. ArrayHelpers.Append(ref json.capabilities, new Capability {path = key, value = value});
  551. }
  552. }
  553. return json;
  554. }
  555. public InputDeviceMatcher ToMatcher()
  556. {
  557. var matcher = new InputDeviceMatcher();
  558. ////TODO: get rid of the piecemeal array allocation and do it in one step
  559. // Interfaces.
  560. if (!string.IsNullOrEmpty(@interface))
  561. matcher = matcher.WithInterface(@interface);
  562. if (interfaces != null)
  563. foreach (var value in interfaces)
  564. matcher = matcher.WithInterface(value);
  565. // Device classes.
  566. if (!string.IsNullOrEmpty(deviceClass))
  567. matcher = matcher.WithDeviceClass(deviceClass);
  568. if (deviceClasses != null)
  569. foreach (var value in deviceClasses)
  570. matcher = matcher.WithDeviceClass(value);
  571. // Manufacturer.
  572. if (!string.IsNullOrEmpty(manufacturer))
  573. matcher = matcher.WithManufacturer(manufacturer);
  574. if (manufacturers != null)
  575. foreach (var value in manufacturers)
  576. matcher = matcher.WithManufacturer(value);
  577. // Product.
  578. if (!string.IsNullOrEmpty(product))
  579. matcher = matcher.WithProduct(product);
  580. if (products != null)
  581. foreach (var value in products)
  582. matcher = matcher.WithProduct(value);
  583. // Version.
  584. if (!string.IsNullOrEmpty(version))
  585. matcher = matcher.WithVersion(version);
  586. if (versions != null)
  587. foreach (var value in versions)
  588. matcher = matcher.WithVersion(value);
  589. // Capabilities.
  590. if (capabilities != null)
  591. foreach (var value in capabilities)
  592. ////FIXME: we're turning all values into strings here
  593. matcher = matcher.WithCapability(value.path, value.value);
  594. return matcher;
  595. }
  596. }
  597. }
  598. }