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