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&lt;DualShock4GamepadHID&gt;( 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&lt;DualShock4GamepadHID&gt;( 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 &gt;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}