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